Banner
{ Deutsch | English }
Tetris

Tetris − LX



Source Code


General thoughts:
For this game I used two different containers: one complete container including the walls and an inner container without the walls (this is the space where the bricks can be moved around). The latter one is used for the graphical display (see the HTML table at the bottom) and the first one for internal processing (see the Spielfeld[] array).
Also in this game I tried to compress everything in one file. There is not a single external graphic needed since everything works with changing background colors of table cells. In this game I used black as the background color of empty cells and other colors for the bricks. Of course you can change these colors as you like but remember to change every occasion of it in the code below.
This array describes the bricks. If you have a 4×4 matrix and name the elements from the lower left to upper right with the formula row×10 + column you can get the four parts of one brick by putting a mark into the element you come to, when you substract one of the numbers of a quartet from the upper left element (33). The four quartets stand for the 4 positions a brick can have.
<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 ];
This array describes the container. It's a matrix of 12 columns and 17 rows. The 1s say an element is set and the 0s say it is empty. You have to turn that matrix upside down and mirror it to get the container, so the lowest row and the first and the last column are already set (these are the walls of the container).
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 ];
Now to the general variable definitions. First we need some counter variables for loops.
var i,j,k;
Now a variable that contains how a brick is rotated. If it's 0 then the brick lays like the first quartet of the Bricks[] array shows, if it is 3 then look at the last quartet.
var Modus;
This is the variable that contains the number of the current brick. This number represents the row in the Bricks[] array (remember, it begins with 0).
var Brick;
This variable is the position where the brick is thrown in. It marks the upper right corner of the brick's 4×4 matrix in the inner container.
var Start = 166;
Position holds the current cell of the upper right corner of the brick's matrix.
var Position;
Those variables contain the positions of the 4 parts of the current brick. The ones below are 4 temporary variables for the same purpose.
var Teil0,Teil1,Teil2,Teil3; var T0,T1,T2,T3;
blah is for recalling a function (you'll see below) and unten is a flag whether a brick reached the ground/another brick or not.
var blah,unten;
This is the color of the current brick, lost checks if the game is over (or paused), Leerzeile is a flag if there was a line to clear and 1 / Speed is the current rate of the stones falling down in seconds.
var color; var lost; var Leerzeile; var Speed;
next contains the next brick, Level is the current level (go figure), Linien is the amount of lines that were cleared in one turn and finally Punkte is the current amount of points.
var next; var Level; var Linien; var Punkte;
Now to the game's functions. This one displays the current brick. At first it determines the positions of the four parts of the brick and stores them in the appropriate variables. Then it checks if it would overlap a brick which is already in the container because then the game is lost (the check if the brick hits the ground comes further below). However if that isn't the case the 4 parts are displayed in the current color.
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; } }
Contrary to the function above this one hides the last drawn brick. Of course you have to delete the last frame before you can draw the next one.
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'; }
This function calculates the cell of the outer part of the container which belongs to Zelle in the inner container. The parameter was is submitted because the inner container doesn't include the walls. So if you need to check if you hit a wall with your brick you also have to submit a value for was so that it returns the (outer container) cell left, right or below the inner container one.
function NiS(Zelle,was) { temp = 11 + (2 * (1 + Math.floor(Zelle / 10))) + (10 * Math.floor(Zelle / 10)) + (Zelle % 10); return temp + was; }
This function is called when the brick is rotated. At first it sets the current Modus variable. Afterwards it puts the new position of the current brick in 4 temporary variables.
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];
This part checks if there is already another brick in the way when you turn the current one or if you would hit the wall by turning. If so the function quits without doing anything, if not it finally rotates the brick, hides the last frame and displays the next one.
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(); }
The next function is either called automatically (when one cycle is over and the brick falls one level) or when the user presses the DOWN key. It only checks if the brick hits another one or he ground (note the use of the was parameter of the NiS() function) and if not is descends it one level.
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; }
The following two functions work basically the same. They are for left/right movement of the current brick.
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(); }
Now this part is for the key detection. Internet Explorer does this different than other browsers so at first there is abrowser check (IE is the only browser that returns true if you call document.all).
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; } }
This function lets the bricks fall. If the game is lost of course it does nothing. If this is not the case it checks if the brick hits the ground with the next movement.
function Verlauf() { if (!lost) { (DOWN()) ? unten = 0 : unten = 1;
Now if the brick would hit the ground the current amount of points is increased, Position, Modus and unten are reset and a new random brick is created.
if (unten == 1) { Punkte += Level; Position = Start; Modus = 0; unten = 0; Brick = Zufallsstein();
Also this is the part where the outer container (the Spielfeld[] array) is updated. Then it is checked if this brick completed a line (the Zeilen() function call). Finally the current amount of points is submitted and the next frame is drawn. Afterwards the function calls itself after a certain amount of time to create an endless loop of falling bricks.
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); } }
This function selects a random brick. At first it is checked if next is 11 (which is only the case when the game just started). Then it must create a next brick at first, otherwise next would already contain a value for the next brick.
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; }
Now the next brick becomes the current one stored in temp and a new random next brick is created.
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;
This part selects the color for the next brick and afterwards draws it into the smaller preview container which in the game is right of the bigger one.
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;
Now the color for the current brick is determinated and the current brick is returned.
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; }
Every game must be initialized. This happens here. This function is called every time you press the New Game button. It hides this button, resets all values, clears the container and starts the game by calling the appropriate functions.
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(); }
This function seeks for complete lines and clears them.
function Zeilen() { Linien = 0; for (i = 0; i < 16; i++) { do {
This loads the first element of a row into temp. Afterwards it's checked if every element in this row is the same in the outer container (since the walls are set to 1 all inner elements have to be 1, too).
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)]) {
Now the line counter is increased (for calculating the points later) and the correct output is created. Then all elements of this row are replaced with the elements of the row above (in the inner and outer container).
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++; } }
This part is needed for the last row which has no row above. So its background color is just replaced with the blank color and the appropriate Spielfeld[] elements are reset to 0.
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); }
Now the points are calculated like explained in the description.
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; } }
If the game is over the New Game button has to be displayed...
function Verloren() { lost = 1; window.document.getElementById('NG').style.visibility = 'visible'; } </SCRIPT>
Now to the HTML part of the game. As I said before the game doesn't use any graphics. Everything works with the change of background colors in the following table:
<TABLE cellspacing="0" cellpadding="0">
This is the left wall cell (of course you can choose another color but grey for it), the first row of inner container cells and the right wall:
<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>
As you can see each cell has it's own ID representing the inner container cell. Now for the rest of the inner container cells:
<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>
... and the floor:
<TR> <TD colspan="12" style="background-color:grey"> </TD> </TR> </TABLE>
This is the small preview table which shows the next brick to come:
<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>
These are the buttons for movement for browsers which don't support keyboard detection (such as Opera). The spacebar is used to rotate bricks but it also hits the button which is currently in focus, so to cause no side effects I remove focus from the button with the this.blur() function.
<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()">
The Pause button (it toggles the lost variable because when this is 1 the Verlauf() function stops):
<INPUT type="button" value="Pause" onClick="this.blur(); if(lost){lost=0; Verlauf(); }else lost=1">
The output fields for the amount of cleared rows, the level number and the current amount of points:
<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">
The New Game button:
<INPUT id="NG" type="button" value="New Game" onClick="this.blur(); init(); ">
Now finally the code that detects if a key is pressed:
<SCRIPT type="text/javascript"> document.onkeydown = Tastendruck; </SCRIPT>