Source-Archiv:http://tetris.brainbuzz.ch/KI-Tetris.zip

Beschreibung des Projekts

Wir haben ein Tetris entwicklet. Der grundsätzliche Spielablauf von Tetris sollte bekannt sein. (Steine fallen vom Himmel, wenn man eine Linie voll hat verschwindet sie). Unser Tetris verfügt über einen Ein-Spieler Modus mit Serverbasierter Hiscore und einen Zwei-Spieler Modus.

Bericht

Aufgabe und Ziele

Wir sollten anhand eines grösseren Projekts die im Unterricht gelernten Flash und Action-Script Kenntnisse praktisch anwenden und vertiefen. Ausserdem sollten wir den Umgang mit Media-Dateien lernen sowie erste Erfahrungen mit der Organisation einens Multimedia-Projekts sammeln.

Unser Ziel war es, ein voll funktionstüchtiges Tetris zu programmieren mit folgenden Features:

  • Ein Player Modus
  • Zwei Player Modus
  • Serverbasierte Hiscore

Organisation und Gesamtzeitaufwand

Der Ablauf unserers Projekts in chronologischer Reihenfolge:

Grunsatzenscheid: Tetris!
Für uns war von Anfang an klar, dass wir ein Spiel entwickeln möchten. Da wir beide Tetris-begeistert sind, wollten wir selbst mal ein Tetris umsetzen.
Planung der Features
Die Hauptfeatures für unser neues Tetris waren auch schnell bestimmt: Einspielermodus mit Hiscore und ein Zweispielermodus mussten es sein.
Research im Internet
Da Tetris ein bekanntes Spiel ist, haben wir im Internet nach einem Tetris in Flash gesucht und auch eines gefunden, von welchem wir einige Ideen übernehmen konnten. Leider konnten wir nicht das ganze Grund-Spiel übernehmen, da der Quellcode nicht unseren Vorstellungen entsprach.
Drehbuch
Wir haben unser Flash in verschiedene Szenen aufgeteilt. Diese waren allsamt relativ einfach umzusetzen und so haben wir auf ein ausführliches Drehbuch verzichtet und stattdessen die Klassenstruktur entwickelt, welche wir weiter unten ausführlich beschreiben.
Design der Klassen/Methoden
Als nächstes haben wir unsere Klassen sowie Methoden designt. Dies nahm einen grossen Anteil unserer Zeit in Anspruch.
Umsetzung der Klassen/Methoden
Zuletzt haben wir die einzelnen Klassen/Methoden ausprogrammiert und im Flash die wenig benötigten Elemente eingefügt.
Layout
Als das Gameplay stand, haben wir das Layout gemacht.
Dokumentation
Zuletzt haben wir diese Dokumentation erstellt.
Gesamtzeitaufwand
Zusammen haben wir über 80 Stunden an diesem Projekt gearbeitet.

Szene-Layout, Organisation und Struktur des Flash-Films

KI-Tetris.fla besteht aus den folgenden Szenen:
Intro
In der Intro Szene kann man den Spielmodus (Ein- resp. Zwei-Spieler) auswählen sowie die aktuelle Hiscore anschauen.
OnePlayer
Hier kann man Tetris im Ein-Spieler Modus spielen.
TwoPlayer
Hier kann man Tetris im Zwei-Spieler Modus spielen. Der hauptsächliche Unterschied zum Ein-Player Modus ist, dass immer wenn man 2 oder mehr Linien gleichzeitig zerstört, der Gegner diese Anzahl Linien minus 1 bekommt. Dem Gegner werden diese Linien von unten ins Spielfeld hineingeschoben.
Hiscore
In dieser Szene wird die Hiscore-Liste aktualisiert und angezeigt. Man hat die Möglichkeit bis zu 100 Einträge anzusehen, mithilfe der zwei Pfeil-Buttons kann geblättert werden. Desweiteren kann man wieder auf die Startseite (Menu) zurückspringen.

Dokumentation der ActionScript-Klassen

Unser Flash-Film ist wie oben beschrieben in vier Szenen aufgeteilt. Praktisch das ganze ActionScript haben wir in externe .as Dateien ausgelagert, deshalb werden wir diese auch intensiver besprechen. Im Flash-Movie selbst wird nur AS für Buttons sowie für die Initialisierung unserer Objekte verwendet. Nach unserer Analyse kamen wir zu folgenden Klassen und Methoden (alle hier dokumentiert):

Tetris (Tetris.as)
Die Tetris-Klasse ist die Haupt-Klasse für ein Tetris Spiel. Initialisiert wird sie mit einem Tetris Clip (beinhaltet Board Clip, Preview Clip und Sound Clip) und einem Steuerungs-Modus sowie dem StartLevel und dem GameMaster Objekt selbst (OnePlayer oder TwoPlayer). Wir haben zwei verschiedene Steuerungen implementiert, einmal mit den Pfeitasten und einmal mit Tasten möglichst links auf der Tastatur:

SteuerungSteuerungsmodus 1Steuerungsmodus 2
LinksPfeil LinksA
RechtsPfeil RechtsD
RotierenPfeil ObenW
UntenPfeil UntenS

  • function createStone()

    createStone nimmt den nächsten Stein (nextStone) aus dem preview und setzt in zuoberst aufs Spielfeld als currentStone, wenn dies möglich ist. Ausserdem wird das Level erhöht, falls bereits genügend Steine heruntergefallen sind.

  • function startMover()                 

    Startet den Intervall, damit der Stein immer nach unten verschoben wird. Geschwindigkeit ist abhängig vom Level.

  • function stopMover()                  

    Stopt den Intervall, damit kein Stein mehr nach unten verschoben wird.

  • function startKeyListener()          

    Startet den KeyListener in Abhängikeit vom Steuerungsmodus 1 oder 2 und startet einen Intervall auf die Funktion checkKeyDown.

  • function stopKeyListener()           

    Stoppt den KeyListener.

  • function onKeyDown()                 

    Wird immer ausgelöst, wenn eine Taste gedrückt wurde. Es wird dann die zur Taste und Steuerungsmodus passende Funktion aufgerufen (moveLeft, moveRight, moveDown, rotate).

  • function checkKeyDown()              

    Wir hatten im Multiplayer Modus das Problem, dass wenn man die Taste nach unten gedrückt hält und der andere Spieler eine Taste drückt, der Stein nicht weiter nach unten beschleunigte, weil wir keinen onKeyDown-Event mehr bekamen. Im Ein-Player Modus war dies kein Problem, da keine andere Taste gedrückt wurde, solange man den Stein nach unten beschleunigen wollte und wir so mehrere onKeyDown-Events bekamen. Dies war ein Problem, da man so x-Mal nach unten drücken musste, um den Stein an den Boden zu bekommen. So haben wir die Funktion checkKeyDown entwickelt, welche regelmässig per setInterval (50ms) aufgerufen wird und falls der nach unten Key gedrückt ist, moveDown ausführt. Für den Steuerungs-Modus 1 sieht das folgendermassen aus:

    			if ( Key.isDown( 40 ) ) {
    				moveDown();
    			}					   
    
    Wir haben dies nur für die "nach unten"-Taste implementiert, da es sonst sehr schwer bis unmöglich war, den Stein an die richtige Stelle (links/rechts) zu verschieben.

  • function moveLeft()                   

    Bewegt den Stein nach links, wenn dies möglich ist.

  • function moveRight()                  

    Bewegt den Stein nach rechts, wenn dies möglich ist.

  • function moveDown() 	               

    Bewegt den Stein nach unten, wenn dies möglich ist. Wenn der Stein unten angekommen ist, werden volle Linien entfernt und die Anzahl Punkte erhöht. Der Game-Master (Klasse OnePlayer oder TwoPlayer) wird informiert, wieviele Linien entfernt wurden. Zum Schluss wird ein neuer Stein erzeugt.

  • function rotate()                     

    Rotiert den Stein, wenn dies möglich ist.

  • function gameOver()                  

    Beendet das Spiel, GameOver Nachricht wird angezeigt. Wird aufgerufen, falls kein neuer Stein auf dem Feld platziert werden konnte oder wenn zuviele Linien im Spielfeld sind (Zwei-Player-Modus).

  • function redrawGame()                

    Aktualisiert das Spielfeld, ruft nur board.redraw() auf.

  • function setLevel( level : Number )   

    Setzt das Level auf den Wert des Parameters und aktualisert die Levelanzeige.

  • function getLevel()                  

    Gibt das aktuelle Level zurück.

  • function setScore( score : Number )   

    Setzt die Punktzahl auf den Wert des Parameters und aktualisiert die Punktanzeige.

  • function getScore()                  

    Gibt den aktuellen Punktestand zurück.

  • function pushLines( nr : Number )     

    Fügt dem Spielfeld von unten neue Linien hinzu, welche ein Loch haben. Falls dies nicht möglich ist, wird gameOver aufgerufen. Wird im Zweispieler Modus vom Game Master aufgerufen.

  • function winner()                    

    Stoppt das Spiel und zeigt "Winner" an. Wird im Zweispieler Modus aufgerufen, falls der Gegner gameOver hat.

Board (Board.as)
Die Board Klasse stellt das Spielfeld. Initialisiert wird sie mit der Höhe, der Breite und dem board-MovieClip. Das wichtigste Attribut der Board Klasse ist ein Zwei-Dimensionaler Array, welcher das Spielfeld darstellt. Ein Board-Objekt verfügt ausserdem für jeden möglichen Stein (Anzahl: Höhe*Breite) über ein Block-MovieClip. Der Block-MovieClip hat für jede Farbe eines Block ein eigenes Frame, welches je nach dem angesprungen wird.
  • function clearField() 

    Das Feld wird gelöscht.

  • function removeFullLines() 

    Löschte alle vollen Linien, gibt die Anzahl gelöschter Linien zurück.

  • function lineFull( y : Number ) 

    Überprüft, ob eine Linie voll ist oder nicht und gibt dies zurück.

  • function lineIsEmpty ( y : Number )

    Überprüft, ob eine Linie leer ist (wird im ZweispielModus gebraucht).

  • function copyLine( srcY : Number, destY : Number ) 

    Kopiert eine Linie in eine andere. Wird gebraucht, wenn die Linien verschoben werden weil Linien entfernt oder hinzugefüt werden.

  • function removeLine( y : Number ) 

    Entfernt eine Linie und schiebt alle darüberliegenden Linien nach unten.

  • function pushLines( nr : Number ) 

    Schiebt alle Linien nr-Anzahl nach oben und füllt unten Linien mit einem Loch auf.

  • function setField( x : Number , y : Number , color : Number) 

    Setzt ein Block im Spielfeld auf die Farbe color+1.

  • function getField( x : Number , y : Number ) 

    Gibt die Farbe eines Blocks zurück. 0 bedeutet leer.

  • function redraw() 

    Zeichnet das Spielfeld neu. Spielt bei allen Blöcken, die sich seit dem letzen redraw geändert haben den Block Clip (block_mc) folgendermassen ab:

      board_mc[blockPos].gotoAndStop(field[y][x]+1);

  • function setWidth( width : Number ) 

    Setzt die Breite des Spielfelds auf den Wert des Parameters.

  • function getWidth() 

    Gibt die Breite des Spielfelds zurück.

  • function setHeight( height : Number ) 

    Setzt die Höhe des Spielfelds auf den Wert des Parameters.

  • function getHeight() 

    Gibt die Höhe des Spielfelds zurück.

Stone (Stone.as)
Die Stone Klasse wird mit einem Board-Objekt initialisiert. Die Klasse stellt die 7 verschiedenen Steine zur Verfügung (im stones-Array). Jeder Stein ist innerhalb eines 5x5 grossen Arrays definiert, beispielsweise die Definition der "langen Stange" sieht so aus:
			[
				[0,0,0,0,0],
				[0,0,0,0,0],
				[1,1,1,1,0],
				[0,0,0,0,0],
				[0,0,0,0,0]
			],
		
1 bedeutet die Farbe. Ein L-Stück ist so definiert:
			[
				[0,0,0,0,0],
				[0,0,0,4,0],
				[0,4,4,4,0],
				[0,0,0,0,0],
				[0,0,0,0,0]
			],
		
und hat somit eine andere Farbe.
  •  function setType( nr : Number ) 

    Setzt den Stein auf den Typ 1 -7, je nach Parameter.

  •  function setRandomType() 

    Generiert einen neuen zufälligen Stein.

  •  function place() 

    Platziert den Stein auf dem Spielfeld (board).

  •  function take() 

    Nimmt den Stein vom Spielfeld (board) weg. Wichtig für Collision-Detection, falls der Stein verschoben wird, damit er sich nicht selbst kollidiert.

  •  function mayPlace( nX : Number , nY  : Number ) 

    Schaut, ob der Stein an die Stelle (nX, nY) gesetzt werden kann. Gibt true zurück, falls dies möglich ist. Dies ist die eigentliche Collision-Detection.

  •  function rotate() 

    Rotiert einen Stein. Da alle 7 Steine in einem 5x5 Array definiert sind, funktioniert das für alle Steine gleich, die Haupt-Routine der Schleife sieht folgendermassen aus:

    		for ( y = 0; y < field.length; y++ ) {
    			for ( x = 0; x < field[y].length; x++ ) {
    				newField[4-x][y] = field[y][x];
    			}
    		}
    			
    				

    rotate

                      [0,0,0,0,0]    [0,0,0,0,0]    [0,0,0,0,0]    [0,0,0,0,0]
                      [0,0,0,4,0]    [0,0,4,4,0]    [0,4,4,4,0]    [0,4,0,0,0]
                      [0,4,4,4,0]    [0,0,0,4,0]    [0,4,0,0,0]    [0,4,0,0,0]
                      [0,0,0,0,0]    [0,0,0,4,0]    [0,0,0,0,0]    [0,4,4,0,0]
                      [0,0,0,0,0]    [0,0,0,0,0]    [0,0,0,0,0]    [0,0,0,0,0]
    					

  •  function getField() 

    Gibt den 5x5 Array zurück.

  •  function setField( is : Array ) 

    Setzt den 5x5 Array.

  •  function getBoard() 

    Gibt das Spielfeld (board) zurück.

  •  function setBoard( board : Board ) 

    Setzt das Spielfeld (board).

Preview (Preview.as)
Diese Klasse übernimmt das Anzeigen des nächsten fälligen Steines auf dem Spielfeld. Initialisiert wird sie im Konstruktor von Tetris.as. Ihr wird eine Instanz des Preview Clips übergeben. Im Konstruktor wird ein Array aus 5x5 Feldern erzeugt, der mit Block Clips (block_mc) abgefüllt wird.
  •  function clearPreview() 

    Löscht alle Blöcke im Preview-Feld, indem alle Block Clips auf das erste Frame gesetzt werden (ein leeres Frame)

  •  function drawNextStone( nextStone : Object ) 

    Als Parameter wird ein Stone Objekt (Stone.as: Instanzname nextStone) erwartet. Der Array aus 5x5 Block Clips wird mithilfe von zwei for-Schleifen bearbeitet. Jeder Block Clip (block_mc) springt an die Stelle, die von dem übergebenen Stone Objekt angegeben (nextStone.field[y][x]) wird.

OnePlayer (OnePlayer.as)
In der OnePlayer-Szene wird ein neues OnePlayer-Objekt instanziert. Dieses wiederum instanziert ein neues Tetris Objekt mit dem Steuerungsmodus 1 und dem ausgewählten StartLevel.
  •  function gameOver( score : Number, control : Number ) 

    Wird vom Tetris-Objekt aufgerufen, wenn GameOver ist. Spielt im _root.replay_mc (Game Over Clip) den 6ten Frame ab.

  •  function linesRemoved ( control : Number, linesRemoved : Number ) 

    Wird immer vom Tetris-Objekt aufgerufen, wenn volle Linien entfernt werden. Ist im OnePlayer ohne eigene Funktion.

TwoPlayer (TwoPlayer.as)
In der TwoPlayer-Szene wird ein neues TwoPlayer-Objekt instanziert. Dieses wiederum instanziert zwei neue Tetris-Objekte (eines mit Steuerungsmodus 1 und eines mit Steuerungsmodus 2) für die beiden Spielfelder.
  •  function gameOver( score : Number, control : Number ) 

    Diese Methode wird von jenem Tetris-Objekt aufgerufen, welches selbst gameOver hat. Bei dem anderen Tetris-Objekt, dem Gewinner, wird die Methode winner aufgerufen. Anschliessend spielt es im _root.replay_mc (Game Over Clip) den 5ten Frame ab.

  •  function linesRemoved ( control : Number, linesRemoved : Number ) 

    Wird immer von demjenigen Tetris-Objekt aufgerufen, dass 2 oder mehr Linien gleichzeitig entfernt hat. Diese Methode ruft dann beim anderen Tetris-Objekt die Methode pushLines auf und schiebt ihm so n-1 Linien unter.

pushLine

Hiscore (Hiscore.as)
Die Hiscore Klasse stellt die Funktionalität zur Verfügung um die Hiscore-Liste mit der Version auf dem Server abzugleichen und diese auf der Hiscore Szene darzustellen. Im Konstruktor wird ein neues Objekt der Klasse LoadVars für die Kommunikation mit dem Server erzeugt. Diesem Objekt wird der username, der score und der gamename (hier "kitetris" - wird benötigt um das richtige hiscore.txt file auf dem Server zu finden) zugewiesen. Ausserdem wird dem onLoad Event dieses Objektes eine Funktion zugewiesen, die sich um die Antwort des Servers kümmert. Falls alles korrekt funktioniert hat, wird die Methode showScores() aufgerufen.
  •  function showScores() 

    Lädt die Antwort des Servers aus dem LoadVars Objekt und weist diese schrittweise der richtigen Textzeile zu.

  •  function last()

    Blättert 10 Resultate zurück.Falls vorgehend keine Resultate mehr sind, wird der Last-Button ausgeblendet.

  •  function next()

    Blättert 10 Resultate vor. Falls es keine weiteren Resultate mehr hat, wird der Next-Button ausgeblendet.

  •  function newGame()

    Springt in das erste Frame der Intro Szene, der Spass kann von neuem beginnen.

Verwendete Quellen

Neave Tetris

Zu Beginn des Projekts haben wir im Internet nach bereits umgesetzen Tetris-Clones in Flash gesucht und fanden das Tetris von Neave (neu nblox). Wir haben den Author per EMail angefragt ob er uns den Quelltext zuschicken könne, dies hat er freundlicherweise gemacht (komplettes Projekt im Ordner used_example).

Der Quelltext entsprach nicht unseren Vorstellung, da er nicht objektorientiert programmiert war und so nur sehr schwer erweiterbar war für den Zwei Player Modus. Wir entschieden uns deshalb, den gesamten Code neu zu programmieren, allerdings haben wir einige Ideen von ihm sowie auch einige MovieClips übernommen. Hier eine Auflistung der übernommen Ideen/Clips:

  • Block Clip: Einzelner Block als MovieClip mit einem Frame pro Farbe (Clip und Idee übernommen)
  • Sound Clip: Der Sound-MovieClip
  • Hiscore: Wurde objektorientiert umgeschrieben, PHP-Datei haben wir nicht stark verändert.
Weil wir den Block Clip verwendet haben, sieht unser Tetris dem von Neave ähnlich. Allerdings unterscheidet sich der Quellcode mit der Ausnahme der Hiscore komplett. Unser Quelltext ist objektorientiert und auf verschiedene Klassen verteilt. Der Quelltext von Neave findet man direkt in der Hauptszene. Auch unterscheidet sich die Spielelogik wesentlich, beispielsweise haben wir die Blöcke anders modelliert als Neave.

Fazit

Es war uns von Beginn an klar, das ein Tetris-Spiel mit unseren Features eine anspruchsvolle Aufgabe darstellte und kaum in der vorgeschlagenen Zeit zu realisieren war. Wie immer in IT-Projekten, war es gegen Ende relativ streng, doch konnten wir alle geplanten Features termingerecht realiseren. Auch haben wir Ideen, wie wir unser Tetris weiter ausbauen können (z.B. MultiPlayer über das Netzwerk).

Uns hat die Arbeit am KI-Tetris viel Freude und ab und zu auch Frust bereitet, Fehlermeldungen im Flash sind eindeutig Mangelware. Das Vorgehen mit einer langen Planungsphase (Klassendesign) hat sich am Schluss jedoch ausgezahlt. Dies war für uns beide das erste Spiel, dass wir in Jahren entwickelt haben.

Sascha Kästle und Philipp Knobel, KI04