Banner
{ Deutsch | English }
Tetris

Tetris − LX



Quellcode


Gedanken zu Beginn:
Für dieses Spiel habe ich 2 Behälter verwendet: einen kompletten inklusive der Wände und einen inneren Behälter ohne die Wände (also nur mit dem Bereich, in dem die Steine bewegt werden). Der letztere wird für die grafische Darstellung benutzt (siehe die HTML-Tabelle weiter unten), wohingegen der erste für die interne Verarbeitung benutzt wird (siehe der Spielfeld[]-Array).
Außerdem habe ich versucht, alles in einer Datei zusammenzufassen. Es werden keine externen Grafiken verwendet, da alles auf wechselnden Farben von Tabellenzellen beruht. Ich habe Schwarz für leere Zellen und andere Farben für die Steine benutzt. Natürlich können die Farben auch angepasst werden, sie müssen dann allerdings auch bei jedem Auftreten im Code geändert werden.
Dieser Array beschreibt die Steine. Wenn man eine 4×4-Matrix hat und die Elemente von unten links nach oben rechts mit der Formel Zeile×10 + Spalte definiert, kann man die 4 Bestandteile eines Steins bestimmen, indem man eine Markierung in jedes Feld setzt, auf welches man kommt, wenn man die Zahlen eines Quartetts vom Element oben links (33) abzieht. Die 4 Quartette stehen für die 4 Positionen, die ein Stein annehmen kann.
<SCRIPT type="text/javascript">
XXXX
var Bricks = [10,11,12,13, 1,11,21,31, 20,21,22,23, 2,12,22,32,
XX
XX
1, 2,11,12, 1, 2,11,12, 1, 2,11,12, 1, 2,11,12,
XX
  XX
2, 3,11,12, 2,12,13,23, 2, 3,11,12, 2,12,13,23,
  XX
XX
1, 2,12,13, 2,11,12,21, 1, 2,12,13, 2,11,12,21,
XXX
X
0,10,11,12, 1, 2,11,21, 10,11,12,22, 1,11,20,21,
X
XXX
3,11,12,13, 2,12,22,23, 11,12,13,21, 1, 2,12,22,
XXX
  X
2,11,12,13, 2,12,13,22, 11,12,13,22, 2,11,12,22 ];
Dieser Array beschreibt den Behälter. Es handelt sich um eine Matrix aus 12 Spalten und 17 Zeilen. Die 1en sind gesetzte Elemente und die 0en leere. Man muss die Matrix nun um 180° drehen, um den eigentlichen Container zu bekommen, sodass die unterste Zeile und die erste und letzte Spalte bereits gesetzt sind (das sind die Wände des Containers).
var Spielfeld = [1,1,1,1,1,1,1,1,1,1,1,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1 ];
Nun zu den allgemeinen Variablendeklarationen. Zuerst brauchen wir ein paar Zählvariablen für Schleifen.
var i,j,k;
Nun eine Variable, die bestimmt, wie ein Stein gedreht ist. Wenn sie 0 ist, dann liegt der Stein wie das erste Quartett im Bricks[]-Array es beschreibt, ist sie 3, muss man sich das letzte Quartett anschauen.
var Modus;
Dies ist die Variable, die die Nummer des aktuellen Steins beinhaltet. Diese Nummer repräsentiert eine Zeile im Bricks[]-Array
var Brick;
Diese Variable ist die Position, an der der Stein in den Behälter gegeben wird. Sie legt die obere rechte Ecke der 4×4-Matrix eines Steins im inneren Behälter fest.
var Start = 166;
Position beinhaltet die aktuelle obere rechte Ecke der Matrix des Steins.
var Position;
Diese Variablen beinhalten die Positionen der 4 Bestandteile des aktuellen Steins, die darunter sind 4 temporäre Variablen zum selben Zweck.
var Teil0,Teil1,Teil2,Teil3; var T0,T1,T2,T3;
blah ist zum wiederholten Aufrufen von Funktionen (wird weiter unten geklärt) und unten ist eine Flag, ob ein Stein bereits den Boden/die Wand berührt oder nicht.
var blah,unten;
Dies ist die Farbe des aktuellen Steins, lost prüft, ob das Spiel vorbei (oder pausiert) ist, Leerzeile ist eine Flag, ob eine Zeile zu löschen ist und 1 / Speed gibt an, in wievielen Sekunden ein Stein eine Zeile nach unten fällt.
var color; var lost; var Leerzeile; var Speed;
next beinhaltet den nächsten Stein, Level ist das aktuelle Level (echt jetz'), Linien ist die Anzahl an Zeilen, die in einem Zug gelöscht wurden und Punkte schließlich ist die aktuelle Anzahl der Punkte.
var next; var Level; var Linien; var Punkte;
Nun zu den Funktionen des Spiels. Diese hier stellt den aktuellen Stein dar. Zuerst werden die Positionen der 4 Bestandteile eines Steins bestimmt und in entsprechenden Variablen abgelegt. Dann wird geprüft, ob dabei bereits ein anderer Stein überlappt wird, denn dann wäre das Spiel verloren (die Prüfung, ob ein Stein den Boden berührt, kommt erst weiter unten). Wenn das nicht zutrifft, bekommen die 4 Teile ihre Farbe zugewiesen.
function showBrick() { Teil0 = Position - Bricks[ 4*Modus + 16*Brick]; Teil1 = Position - Bricks[1 + 4*Modus + 16*Brick]; Teil2 = Position - Bricks[2 + 4*Modus + 16*Brick]; Teil3 = Position - Bricks[3 + 4*Modus + 16*Brick]; if (Spielfeld[NiS(Teil0,0)] || Spielfeld[NiS(Teil1,0)] || Spielfeld[NiS(Teil2,0)] || Spielfeld[NiS(Teil3,0)]) Verloren(); else { window.document.getElementById('S' + Teil0).style.backgroundColor = color; window.document.getElementById('S' + Teil1).style.backgroundColor = color; window.document.getElementById('S' + Teil2).style.backgroundColor = color; window.document.getElementById('S' + Teil3).style.backgroundColor = color; } }
Im Gegensatz zur vorherigen Funktion versteckt diese Funktion den zuletzt dargestellten Stein. Natürlich muss der letzte Frame erst gelöscht werden, bevor ein neuer dargestellt wird.
function hideBrick() { window.document.getElementById('S' + Teil0).style.backgroundColor = 'black'; window.document.getElementById('S' + Teil1).style.backgroundColor = 'black'; window.document.getElementById('S' + Teil2).style.backgroundColor = 'black'; window.document.getElementById('S' + Teil3).style.backgroundColor = 'black'; }
Diese Funktion berechnet zur Zelle im inneren Behälter die analoge Zelle im äußeren. Der Parameter was wird mit gesendet, da der innere Behälter keine Wände beinhaltet. Um also zu prüfen, ob mit dem Stein bereits die Wand berührt wird, muss ein Wert für was übermittelt werden, sodass es die Zelle (im äußeren Behälter) rechts, links und unter der im inneren Behälter zurückgibt.
function NiS(Zelle,was) { temp = 11 + (2 * (1 + Math.floor(Zelle / 10))) + (10 * Math.floor(Zelle / 10)) + (Zelle % 10); return temp + was; }
Diese Funktion wird aufgerufen, wenn der Stein rotiert werden soll. Zuerst wird die aktuelle Modus-Variable gesetzt und anschließend die neue Position des Steins in 4 temporären Variablen abgelegt.
function UP() { if (Modus < 3) Temp = Modus + 1; else Temp = 0; T0 = Position - Bricks[ 4*Temp + 16*Brick]; T1 = Position - Bricks[1 + 4*Temp + 16*Brick]; T2 = Position - Bricks[2 + 4*Temp + 16*Brick]; T3 = Position - Bricks[3 + 4*Temp + 16*Brick];
Dieser Teil prüft, ob bereits ein Stein im Weg ist, wenn der aktuelle gedreht wird, oder ob man mit der Drehbewegung die Wand berühren würde. Ist dies der Fall, wird die Funktion ohne weitere Aktionen beendet, ansonsten wird der Stein endlich gedreht, der letzte Frame versteckt und der neue dargestellt.
if ( Spielfeld[NiS(T0,0)] || Spielfeld[NiS(T1,0)] || Spielfeld[NiS(T2,0)] || Spielfeld[NiS(T3,0)] || (((T0%10 < 1) || (T1%10 < 1) || (T2%10 < 1) || (T3%10 < 1)) && ((T0%10 > 8) || (T1%10 > 8) || (T2%10 > 8) || (T3%10 > 8)) || ((T0 < 12) || (T1 < 12) || (T2 < 12) || (T3 < 12))) ) return false; hideBrick(); if (Modus < 3) Modus++; else Modus = 0; showBrick(); }
Die nächste Funktion wird entweder automatisch (wenn ein Zyklus vorbei ist und der Stein eine Ebene tiefer fällt) oder wenn der Benutzer die DOWN-Taste betätigt, aufgerufen. Sie prüft lediglich, ob der Stein einen weiteren oder den Boden berührt (man beachte die Benutzung des was-Parameters der NiS()-Funktion). Ist dem nicht so, wird der Stein eine Ebene abgesenkt.
function DOWN() { if (Spielfeld[NiS(Teil0,-12)] || Spielfeld[NiS(Teil1,-12)] || Spielfeld[NiS(Teil2,-12)] || Spielfeld[NiS(Teil3,-12)]) return false; hideBrick(); Position -= 10; showBrick(); return true; }
Die folgenden beiden Funktionen funktionieren quasi genauso. Sie sind für die Links-/Rechts-Bewegungen des aktuellen Steins.
function LEFT() { if (Spielfeld[NiS(Teil0,-1)] || Spielfeld[NiS(Teil1,-1)] || Spielfeld[NiS(Teil2,-1)] || Spielfeld[NiS(Teil3,-1)]) return false; hideBrick(); Position--; showBrick(); } function RIGHT() { if (Spielfeld[NiS(Teil0,1)] || Spielfeld[NiS(Teil1,1)] || Spielfeld[NiS(Teil2,1)] || Spielfeld[NiS(Teil3,1)]) return false; hideBrick(); Position++; showBrick(); }
Dieser Teil ist nun für die Tastendruck-Kontrolle. Internet Explorer macht dies anders als andere Browser, also erfolgt zuerst ein Browser-Check (IE ist der einzige Browser, der true zurückgibt, wenn document.all aufgerufen wird.
function Tastendruck(Druck) { if (document.all) k = window.event.keyCode; else k = Druck.which; if ((k == 38 || k == 32 || k == 87) && lost == 0) UP(); if ((k == 37 || k == 65) && lost == 0) LEFT(); if ((k == 39 || k == 68) && lost == 0) RIGHT(); if ((k == 40 || k == 83) && lost == 0) DOWN(); if (k == 80) { if (lost && document.getElementById('NG').style.visibility == "hidden") { lost = 0; Verlauf(); } else lost = 1; } }
Diese Funktion lässt den Stein fallen. Wenn lost gesetzt ist, passiert natürlich nichts. Ist dies nicht der Fall, wird geprüft, ob der Stein im nächsten Zug den Boden berührt.
function Verlauf() { if (!lost) { (DOWN()) ? unten = 0 : unten = 1;
Wenn der Stein nun den Boden berührt, wird die aktuelle Punktzahl erhöht, Position, Modus und unten zurückgesetzt und ein neuer Zufallsstein generiert.
if (unten == 1) { Punkte += Level; Position = Start; Modus = 0; unten = 0; Brick = Zufallsstein();
Dies ist der Teil, in dem der äußere Behälter (der Spielfeld[]-Array) aktualisiert wird. Dann wird geprüft, ob der Stein eine Zeile vervollständigt hat (der Zeilen()-Funktionsaufruf). Letztendlich wird die neue Punktzahl gesendet und der nächste Frame gezeichnet. Abschließend ruft sich die Funktion nach einer bestimmten Zeit selbst wieder auf, um eine endlose Schleife von fallenden Steinen zu erzeugen.
Spielfeld[NiS(Teil0,0)] = 1; Spielfeld[NiS(Teil1,0)] = 1; Spielfeld[NiS(Teil2,0)] = 1; Spielfeld[NiS(Teil3,0)] = 1; Zeilen(); document.getElementById('Points').value = Punkte; showBrick(); } blah = setTimeout("Verlauf()",1000/Speed); } }
Diese Funktion wählt zufällig einen neuen Stein aus. Zuerst wird geprüft, ob next 11 ist (dies ist nur der Fall, wenn das Spiel gerade erst gestartet wurde). In diesem Fall muss zuerst ein next-Stein generiert werden, ansonsten enthälst next bereits einen Wert für den nächsten Stein.
function Zufallsstein() { if (next == 11) { next = Math.random(); if (next <= 1/17) next = 0; else if (next <= 3/17) next = 1; else if (next <= 6/17) next = 2; else if (next <= 9/17) next = 3; else if (next <= 12/17) next = 4; else if (next <= 15/17) next = 5; else next = 6; }
Jetzt wird der next-Stein in temp abgelegt und ein neuer Zufallsstein erzeugt.
temp = next; next = Math.random(); if (next <= 1/17) next = 0; else if (next <= 3/17) next = 1; else if (next <= 6/17) next = 2; else if (next <= 9/17) next = 3; else if (next <= 12/17) next = 4; else if (next <= 15/17) next = 5; else next = 6;
Dieser Teil bestimmt die Farbe des nächsten Steins und zeichnet den neuen Stein anschließend in den kleinen Vorschau-Behälter, der sich im Spiel rechts neben dem großen Behälter befindet.
switch (next) { case 0: color = '#0206AC'; break; case 1: color = 'darkblue'; break; case 2: color = 'darkred'; break; case 3: color = 'purple'; break; case 4: color = '#B67600'; break; case 5: color = 'green'; break; case 6: color = 'darkgreen'; break; } for (i = 0; i < 4; i++) { for (j = 0; j < 4; j++) { window.document.getElementById('P' + (i*10 + j)).style.backgroundColor = 'black'; } } window.document.getElementById('P' + (33 - Bricks[ 16*next])).style.backgroundColor = color; window.document.getElementById('P' + (33 - Bricks[1 + 16*next])).style.backgroundColor = color; window.document.getElementById('P' + (33 - Bricks[2 + 16*next])).style.backgroundColor = color; window.document.getElementById('P' + (33 - Bricks[3 + 16*next])).style.backgroundColor = color;
Jetzt wird die Farbe des aktuellen Steins festgelegt und der Stein zurückgegeben.
switch (temp) { case 0: color = '#0206AC'; break; case 1: color = 'darkblue'; break; case 2: color = 'darkred'; break; case 3: color = 'purple'; break; case 4: color = '#B67600'; break; case 5: color = 'green'; break; case 6: color = 'darkgreen'; break; } return temp; }
Jedes Spiel muss initialisiert werden. Das passiert hier. Diese Funktion wird immer dann aufgerufen, wenn der Neues Spiel-Button gedrückt wird. Sie versteckt den Button, setzt alle Werte zurück, leert den Behälter und startet das Spiel, indem sie die nötigen Funktionen aufruft.
function init() { window.document.getElementById('NG').style.visibility = 'hidden'; Spielfeld = [ 1,1,1,1,1,1,1,1,1,1,1,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1 ]; for (i = 0; i < 10; i++) { for (j = 0; j < 17; j++) { window.document.getElementById('S' + (j*10 + i)).style.backgroundColor = 'black'; } } Position = Start; next = 11; document.getElementById('Zeilen').value = "0"; document.getElementById('Level').value = "1"; document.getElementById('Points').value = "0"; Level = 1; Punkte = 0; Brick = Zufallsstein(); Modus = 0; showBrick(); lost = 0; Speed = 1; Verlauf(); }
Diese Funktion sucht nach vollständigen Zeilen und löscht sie.
function Zeilen() { Linien = 0; for (i = 0; i < 16; i++) { do {
Hier wird das erste Element einer Zeile in temp abgelegt. Danach wird geprüft, ob alle Elemente dieser Zeile gleich sind im äußeren Behälter (da die Wände auf 1 gesetzt sind, müssen alle inneren Elemente auch 1 sein).
temp = NiS(i * 10,0); if (Spielfeld[temp] && Spielfeld[(temp+1)] &&Spielfeld[(temp+2)] && Spielfeld[(temp+3)] && Spielfeld[(temp+4)] && Spielfeld[(temp+5)] && Spielfeld[(temp+6)] && Spielfeld[(temp+7)] && Spielfeld[(temp+8)] && Spielfeld[(temp+9)]) {
Nun wird der Zeilen-Zähler erhöht (um später die Punkte zu berechnen) und die korrekte Ausgabe generiert. Danach werden alle Elemente dieser Zeile im inneren und äußeren Behälter mit denen der Zeile darüber ersetzt.
Linien++; Leerzeile = 1; document.getElementById('Zeilen').value++; Level = Math.floor(document.getElementById('Zeilen').value / 10) + 1; document.getElementById('Level').value = Level; if (document.getElementById('Zeilen').value%10 == 0) Speed *= 1.3; for (j = i; j < 16; j++) { k = 0; temp = NiS(j * 10,0); while (k < 10) { window.document.getElementById('S' + (j*10 + k)).style.backgroundColor = window.document.getElementById('S' + (j*10 + k + 10)).style.backgroundColor; Spielfeld[temp+k] = Spielfeld[temp + 12 + k]; k++; } }
Dieser Teil wird für die letzte Zeile benötigt, welche keine Zeile darüber besitzt. Also wird ihre Hintergrundfarbe einfach mit der Farbe für leere Zellen ersetzt und die entsprechenden Spielfeld[]-Werte auf 0 gesetzt.
k = 0; temp = NiS(160,0); while (k < 10) { window.document.getElementById('S' + (160 + k)).style.backgroundColor = 'black'; Spielfeld[temp+k] = 0; k++; } } else Leerzeile = 0; } while (Leerzeile); }
Jetzt werden die Punkte berechnet, wie es in der Beschreibung geschildert ist.
switch(Linien) { case 1: Punkte += (50 * Level); break; case 2: Punkte += (100 * Level); break; case 3: Punkte += (300 * Level); break; case 4: Punkte += (1200 * Level); break; } }
Wenn das Spiel vorbei ist, muss der Neues Spiel-Button eingeblendet werden...
function Verloren() { lost = 1; window.document.getElementById('NG').style.visibility = 'visible'; } </SCRIPT>
Nun zum HTML-Teil des Spiels. Wie ich bereits sagte, benutzt das Spiel keinerlei Grafiken. Alles funktioniert über das Verändern von Hintergrundfarben in den Zellen folgender Tabelle:
<TABLE cellspacing="0" cellpadding="0">
Dies ist die linke Wand (natürlich kann man hier auch eine andere Farbe als Grau benutzen), die erste Zeile des Behälters und die rechte Wand:
<TR> <TD rowspan="17" style="background-color:grey"> </TD> <TD id="S160"> </TD> <TD id="S161"> </TD> <TD id="S162"> </TD> <TD id="S163"> </TD> <TD id="S164"> </TD> <TD id="S165"> </TD> <TD id="S166"> </TD> <TD id="S167"> </TD> <TD id="S168"> </TD> <TD id="S169"> </TD> <TD rowspan="17" style="background-color:grey"> </TD> </TR>
Wie man sieht, hat jede Zelle ihre eigene ID, welche die Zelle im inneren Behälter repräsentiert. Nun zum Rest der Zellen:
<TR> <TD id="S150"> </TD> <TD id="S151"> </TD> <TD id="S152"> </TD> <TD id="S153"> </TD> <TD id="S154"> </TD> <TD id="S155"> </TD> <TD id="S156"> </TD> <TD id="S157"> </TD> <TD id="S158"> </TD> <TD id="S159"> </TD> </TR> . . . <TR> <TD id="S0"> </TD> <TD id="S1"> </TD> <TD id="S2"> </TD> <TD id="S3"> </TD> <TD id="S4"> </TD> <TD id="S5"> </TD> <TD id="S6"> </TD> <TD id="S7"> </TD> <TD id="S8"> </TD> <TD id="S9"> </TD> </TR>
... und der Boden:
<TR> <TD colspan="12" style="background-color:grey"> </TD> </TR> </TABLE>
Dies ist die kleine Vorschautabelle, die den nächsten Stein anzeigt:
<TABLE cellpadding="0" cellspacing="0"> <TR> <TD style="background-color:grey" colspan="6"> </TD> </TR> <TR> <TD style="background-color:grey" rowspan="4"> </TD> <TD id="P30"> </TD> <TD id="P31"> </TD> <TD id="P32"> </TD> <TD id="P33"> </TD> <TD style="background-color:grey" rowspan="4"> </TD> </TR> <TR> <TD id="P20"> </TD> <TD id="P21"> </TD> <TD id="P22"> </TD> <TD id="P23"> </TD> </TR> <TR> <TD id="P10"> </TD> <TD id="P11"> </TD> <TD id="P12"> </TD> <TD id="P13"> </TD> </TR> <TR> <TD id="P0"> </TD> <TD id="P1"> </TD> <TD id="P2"> </TD> <TD id="P3"> </TD> </TR> <TR> <TD style="background-color:grey" colspan="6"> </TD> </TR> </TABLE>
Dies sind die Bewegungs-Buttons für die Browser, die keine Tastenkontrolle erlauben (wie zum Beispiel Opera). Die Leertaste wird normalerweise benutzt, um Steine zu drehen, aber sie drückt auch den aktuell ausgewählten Button. Daher wird der Fokus mit der this.blur()-Funktion entfernt, um Nebeneffekte zu vermeiden.
<INPUT type="button" value="&uArr; " onClick="this.blur(); UP()"> <INPUT type="button" value="&lArr; " onClick="this.blur(); LEFT()"> <INPUT type="button" value="&dArr; " onClick="this.blur(); DOWN()"> <INPUT type="button" value="&rArr; " onClick="this.blur(); RIGHT()">
Der Pause-Button (er alterniert die lost-Variable, denn wenn diese 1 ist, hält die Verlauf()-Funktion an):
<INPUT type="button" value="Pause" onClick="this.blur(); if(lost){lost=0; Verlauf(); }else lost=1">
Die Ausgabefelder für die Anzahl der gelöschten Zeile, das Level und den Punktestand:
<INPUT type="text" id="Zeilen" size="7" value="0"> <INPUT type="text" id="Level" size="7" value="0"> <INPUT type="text" id="Points" size="7" value="0">
Der Neues Spiel-Button
<INPUT id="NG" type="button" value="New Game" onClick="this.blur(); init(); ">
Und letztendlich der Code, der prüft, ob eine Taste gedrückt wurde:
<SCRIPT type="text/javascript"> document.onkeydown = Tastendruck; </SCRIPT>