100% found this document useful (2 votes)
160 views31 pages

Make Your Own Game

Making a [simple] tetris type game complete code of game
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
100% found this document useful (2 votes)
160 views31 pages

Make Your Own Game

Making a [simple] tetris type game complete code of game
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
You are on page 1/ 31

Making a [simple] tetris type game

By Shahzad Arain
http://www.pakdata.net
[email protected]
92-334-5307738
92-334-9564004

This document and the accompanying HTML files show how to construct a game similar
to the popular falling down blocks game called tetris invented by Shahzad Arain Pakistan.
The game has 4-block pieces, 7 distinct shapes that fall from the top. The challenge is to
manipulate them by horizontal and rotation moves so that lines get completely filled up.
You receive points for completing lines. Filled lines are removed. Completing more than
one line (up to 4) at a time results in more points. My so-called 'tinytetris' begins like
this:

A complete game may look like this:

DRAFT 1
There are several improvements to make to this game to make it more resemble the 'real'
game: modify the start game action so a new game can be started; change the scoring to
involve speeding up and other features; and implement the actual down button (in the real
game, you gain points by moving a piece all the way down in one move). The grace
period implementation also may need improvement. A pause button would be nice.
I came across an on-line tetris-like game done in JavaScript by Hiro Nakamura and was
inspired to create my own game, with accompanying notes. Though I did not take many
features from Nakamura's game, it served as inspiration to make the attempt to get
something working in JavaScript. Most 'industrial-strength' games are written in
compiled languages such as c++ or Java or application development tools such as Flash.
This JavaScript implementation probably is not very robust. A fast player could click the
buttons too fast for the underlying program to perform—finish the service cycle before it
must start again.
The main lessons to be learned from this work come from studying the process of
building an application revealed in these notes and the documents. Two critical features
of the process are
• I proceeded incrementally, as shown by examination of the files: tinytetris,
tinytetris1 through tinytetris10. I put off inserting timed action until the very last
stage.

DRAFT 2
• I implemented essentially a development environment to test the program without
having to play the game. For example, I made hyperlink style buttons, easily
changeable, to create specific pieces and to move the current piece one unit down
the board. These links were removed for the final version of the game.
It is possible to skip to the exposition of the final program, tinytetris10, which does
contain line by line explanations. However, the 'build-up' has value, especially for new
programmers.
I used the Mozilla browser for its JavaScript console. This facilitated finding errors such
as mis-matched brackets. I used Paint Shop Pro to create image files of 8 blocks. One,
bblock.gif, is used for the blank or open space. The seven others are different colors and
have borders.

Design decisions
For my 'tiny' tetris program, three design decisions deserve special mention.
I chose an essentially static approach. No element moves. Instead, the board contains a
sequence of img tags laid out in a grid. The contents of these img tags (the src values)
change. This makes it easy to determine when a row (line) of the board is filled.
Looking back at my other examples, think of coin toss and image swap and not bouncing
ball or cannonball. Doing it this way means I do not have to worry about browser
differences.
The falling shapes are sets of blocks, four blocks each, put together using what I term
formulas. I did this because the shapes must be considered as separate blocks once they
hit down.
The left, right, rotate and down buttons are implemented as form submit buttons. This
presents an obvious set of options for the player and I do not need to be concerned with
key codes, which can vary with different keyboards.

Development stages
These development stages are not / were not perfect. There were cases in which I needed
to go back and change tactics. However, the whole project was done quickly: 2 days
(and I still went to aerobics, attended a party, had company at the house, and did some
grass-roots political action).
tinytetris create board
tinytetris1 create pieces (2 different ones) using formula in a table
tinytetris2 create pieces of all 7 types. A button can be easily changed to make a
different shape.
tinytetris3 start implementation of left and right moves and rotate
tinytetris4 checks for room for new piece—this is, check if game over
tinytetris5 move current piece down a row (invoked by button). Check if hit bottom
tinytetris6 Add places to put lines and scores (not yet used). Counts lines but only if
player attempts to move down after hitting down.
DRAFT 3
tinytetris7 Add check that piece has hit down and can't go further using
checkifhitdown function. Made change to completefalling function to ease
next step. Added new testing option to get different block types.
tinytetris8 Remove filled lines (cascade down)
tinytetris9 Automatic new, random block when player clicks start game and when
block touches down. Also, added call to checkifhitdown in rotate and
moveover.
tinytetris10 Use setInterval to fall automatically. Set up grace period after touchdown
to move current piece, thus allowing horizontal move after piece touches
down. This involved changing how completefalling is called.

tinytetris

The general outline for the program (loosely following Nakamura) is to create the board
by creating a two-dimensional grid of img tags (I am avoiding using the term table).
These image tags are all in one <td> element. The screen is:

It certainly is not obvious, but this board contains 9 times 15 img tags. The following
made up screen shot shows, somewhat crudely, how the img tags are laid out. The img
tags initially hold a borderless white block held in the file bblock.gif. The images are all

DRAFT 4
the same size. The game pieces consist of arrangements of blocks of 7 different colors.
These blocks have borders.

The code is

<html>
<head>
<title>Simple Tetris </title>
<script language="JavaScript">

/* createboard of images */

var hwidth = 9; //number of columns


var vheight = 15; //number of rows

function createboard() {
var i;
var j;
for (i=0; i<vheight; i++) {
for (j=0; j<hwidth; j++) {
document.write("<img src='bblock.gif'/>");
}
document.write("<br/>");
}
}
</script>
</head>
<body>
<table border="1"> // border of the board
<td>
<script language="JavaScript">
createboard();
</script>
</td>
</table>

DRAFT 5
<img src="bblock.gif"/>
<a href="javascript:startgame();">Start Game </a>
</body>
</html>

The createboard function is called from within a script element in the <body> element. It
uses what will become a very familiar construction of nested for loops. The variables
vheight and hwidth hold the number of columns and rows, respectively. After each
complete iteration of the inner for loop, a <br/> tag is output to go to the next row. At this
point, you may ask how the img tags can be accessed. The answer is by using the so-
called images collection of the document object. The expression
document.images[imgno].src
can be used to access or set the img tag indicated by the number imgno. A function
called imagenumber, to be described below, will convert from column and row to image
number.
The <a> hyperlink that calls startgame is not yet functional.
**********************************************

tinytetris1

A critical feature of this game is the 4-block sets. Each shape is prescribed by a formula.
(When we get to rotation, we will use an array of arrays of arrays to designate the
formula for each orientation of each of the 7 types.) A formula specifies the x and y
offset from an origin for each of the 4 blocks. In the code below, formulas are given for
the T shape and the straight line shape. The new function is makeblock. It is invoked at
this stage by code in an <a> link. Note also the imagenumber function for generating a
number to use to indicate which image in the images collection corresponds to a given
column and row pair. [See the file for the complete code. This just shows the new
material.]
<html>
<head>

var blockformulas = [
[[0,0],[1,0],[2,0],[1,1]], // T shape
[[0,0],[1,0],[2,0],[3,0]] // straight line
];
var blockimages = [
"darkblue.gif",
"lightblue.gif"
];
// generates the image tag number from col and row
function imagenumber(atcol, atrow) {
var imagenum = atrow*hwidth + atcol;
return imagenum;
}
//make a block of type type at column atcol and at row atrow
//used to start off blocks

DRAFT 6
function makeblock(type, atcol, atrow) {
var i;
var block = blockimages[type];
var formula = blockformulas[type];
var imagenum;
for (i=0;i<=3;i++) {
imagenum=imagenumber(atcol+formula[i][0], atrow+formula[i][1]);
document.images[imagenum].src = block;
}
alert("end of makeblock");
}

</script>
</head>
<body>

<a href="javascript:makeblock(1,2,0)">Make block 1 2 0 </a>


</body>
</html>
**************************************************************

tinytetris2
Once the last program worked, I had the confidence to create the rest of the formulas. The
javascript in the <a> tag was changed to check each of the 7 formulas.

var blockformulas = [
[[0,0],[1,0],[2,0],[1,1]],
[[0,0],[1,0],[2,0],[3,0]],
[[0,1],[1,1],[1,0],[2,0]],
[[0,0],[1,0],[0,1],[1,1]],
[[0,0],[1,0],[1,1],[2,1]],
[[0,0],[1,0],[2,0],[2,1]],
[[0,1],[1,1],[2,1],[2,0]]
];
var blockimages = [
"darkblue.gif",
"lightblue.gif",
"green.gif",
"yellow.gif",
"red.gif",
"purple.gif",
"gray.gif"
];

DRAFT 7
*********************************

tinytetris3

The challenge in this stage is to add the horizontal moves and the rotate move. The first
step is to provide buttons for the player. This is done by using a table for layout and
putting a <form> containing <input type="button"> tags all in the <body>. These buttons
invoke moveover with an argument indicating the direction and rotate(). The rotate does
not take any argument. Instead, its invocation goes through a fixed sequence of what I
term orientations. The rotation operation required a very large array, orientations of the
same type of formulas as described before. The first element (0th element) of orientations
is the same as blockformulas. I decided not to combine these two. It took some testing to
get these correct.
Doing the horizontal move or even the rotation turned out to be easy. The problem is that
it is necessary to check if a move is possible, that is, not blocked by the edges of the
board or other pieces. The check for the edges makes use of the modulus operator (%).
The code makes use of the break; statement to get out of a for loop. This code uses what
I call 'oksofar coding'. A variable is initialized to true and then set to false if and when
something is detected.
The problem in checking for conflicts with other shapes is that it is necessary to
distinguish between spaces occupied by the current piece and spaces occupied by other
shapes. Spaces occupied by blocks in the current piece may be vacated to make room for
others. This required a multi-step procedure (with several nested for loops) in which it is
necessary to restore blocks if a conflict is detected.
The code to detect if some shape occupies a given position is done by doing a string
search (search method of string object) on the document.images[ ].src (AFTER using
String to convert this to a String from some other internal form). This is necessary
because this string is very long, containing the whole file locator, not just 'bblock.gif' or
'darkblue.gif', etc. My method is to search for 'bblock.gif' and if it is not found, this
indicates that this <img> contains an actual shape.

//block formulas for 4 orientations
//orientations[orient][type][block 0 to 3][x and y]
var orientations = [
[
[[0,0],[1,0],[2,0],[1,1]], //
[[0,0],[1,0],[2,0],[3,0]], //
[[0,1],[1,1],[1,0],[2,0]], //
[[0,0],[1,0],[0,1],[1,1]], //
[[0,0],[1,0],[1,1],[2,1]], //
[[0,0],[1,0],[2,0],[2,1]], //
[[0,1],[1,1],[2,1],[2,0]] //
],
[
[[1,0],[1,1],[1,2],[2,1]], //
[[1,0],[1,1],[1,2],[1,3]], //
[[1,2],[1,1],[0,1],[0,0]], //
[[0,0],[1,0],[0,1],[1,1]], //

DRAFT 8
[[1,0],[1,1],[0,1],[0,2]], //
[[1,2],[1,1],[1,0],[2,0]], //
[[2,2],[2,1],[2,0],[1,0]] //
],
[
[[0,1],[1,1],[2,1],[1,0]], //
[[0,0],[1,0],[2,0],[3,0]], //
[[2,0],[1,0],[1,1],[0,1]], //
[[0,0],[1,0],[0,1],[1,1]], //
[[0,0],[1,0],[1,1],[2,1]], //
[[2,1],[1,1],[0,1],[0,0]], //
[[2,0],[1,0],[0,0],[0,1]] //
],
[
[[1,0],[1,1],[1,2],[0,1]], //
[[1,0],[1,1],[1,2],[1,3]], //
[[1,2],[1,1],[0,1],[0,0]], //
[[0,0],[1,0],[0,1],[1,1]], //
[[1,0],[1,1],[0,1],[0,2]], //
[[1,0],[1,1],[1,2],[0,2]], //
[[1,0],[1,1],[1,2],[2,2]] //
]
];

var current = [ //image number, column and row of each of 4 blocks


[0,0,0],
[0,0,0],
[0,0,0],
[0,0,0]
];
var currenttype; //image file name
var currenttypenum; //0 to 6
var currentorientation; //0 to 3
var currentorigin; // nominal origin of whole 4-block piece

//make a block of type type at column atcol and at row atrow


//used to start off blocks
function makeblock(type, atcol, atrow) {
//need to check if room to add block
currentorigin = [atcol, atrow];
currenttypenum = type;
currenttype = blockimages[type];
currentorientation = 0;
var i;
var block = blockimages[type];
var formula = blockformulas[type];
var imagenum;
var atc;
var atr;
for (i=0;i<=3;i++) {
atc = atcol + formula[i][0];
atr = atrow + formula[i][1];
imagenum=imagenumber(atc, atr);

DRAFT 9
// will add check for room to add block. If none, end game.
document.images[imagenum].src = block;
current[i][0]=imagenum;
current[i][1] = atc;
current[i][2] = atr;
}

}
// move left (-1) or right (1)
function moveover(dir) {

var i;
var tests;
var oksofar = true;
var imgno;
var newcurrent = new Array();
var saved = new Array();
for (i=0; i<=3; i++) {
imgno = current[i][0];

if (dir==-1) { // moving left


if (0 == imgno % hwidth) // at left edge
{ oksofar = false;

break; } }
if (dir == 1) { // moving right
if ((hwidth-1)== imgno % hwidth) { //at right edge
oksofar = false;

break; }
}

newcurrent[i] = imgno+dir;
}
// if oksofar (no blocks at critical edge, newcurrent is set

if (oksofar) {
for (i=0; i<=3; i++) {
saved[i] = current[i][0];
document.images[current[i][0]].src = "bblock.gif";
}

for (i=0; i<=3; i++) {

tests = String(document.images[newcurrent[i]].src);
found = tests.search("bblock.gif");
if (found == -1) { // meaning it was not found
oksofar = false;
break;
}
}

if (oksofar) {
for (i=0;i<=3;i++) {
document.images[newcurrent[i]].src= currenttype;
current[i][0] = newcurrent[i];
current[i][1] = current[i][1]+dir;

DRAFT 10
}

currentorigin[0]=currentorigin[0]+dir;
}
else {
for (i=0;i<=3;i++) {
document.images[saved[i]].src = currenttype; //restore
}
}
}
}

// rotate current blocks


function rotate() {
var block = currenttype;
var savedorientation = currentorientation;
currentorientation = (currentorientation+1) % 4; //rotates to next
orientation
var i;
var formula = orientations[currentorientation][currenttypenum];
var atcol = currentorigin[0];
var atrow = currentorigin[1];
var atc;
var atr;
var tests;
var newcurrent = Array();
var saved = Array();
var oksofar = true;
// calculate new imagenumbers & chk if over right side
for (i=0;i<=3;i++) {
atc = atcol + formula[i][0];
if (atc>=(hwidth-1)) {
oksofar = false;
break; }
if (atc<0) {
oksofar = false;
break; }
atr = atrow + formula[i][1];
if (atr>=(vheight-1)) {
oksofar = false;
break; }
newcurrent[i]=imagenumber(atc, atr);
}
if (oksofar) {
for (i=0;i<=3;i++) { //save then clear slots
saved[i] = current[i][0];
document.images[current[i][0]].src = "bblock.gif" }
// now go through and check each target slot for block
for (i=0;i<=3;i++) {
tests = String(document.images[newcurrent[i]].src);
found = tests.search("bblock.gif");
if (found == -1) { // meaning it was not found
oksofar = false;
break; }
}
if (oksofar) {
for (i=0;i<=3;i++) {

DRAFT 11
imagenum=newcurrent[i];
document.images[imagenum].src = block;
current[i][0]=imagenum;
current[i][1] = atcol+formula[i][0];
current[i][2] = atrow+formula[i][1];
}
}
else { //need to restore from saved
for (i=0;i<=3;i++) {
document.images[saved[i]].src = block;
}
currentorientation = savedorientation;
}
} //close first if oksofar
else {
currentorientation = savedorientation;
}
} // close function

</script>
</head>
<body>
<table>
<td>
<table border="1">
<td>
<script language="JavaScript">
createboard();
</script>
</td>
</table>
</td>
<td>
<form>
<input type="button" onClick="moveover(-1);" value="left">
<input type="button" onClick="rotate();" value="rotate">
<input type="button" onClick="moveover(1);" value="right">
<br/>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
<input type="button" onClick="movedown();" value="down">
</form>
</td>
</table>
<img src="bblock.gif"/>
<a href="javascript:startgame();">Start Game </a> <br/>
<a href="javascript:makeblock(5,5,1);">Make block 5 5 0 </a> <br/>
<a href="javascript:rotate();">Rotate </a>
</body>
</html>

***********************************

tinytetris4

function makeblock(type, atcol, atrow) {


var tests;

DRAFT 12
var found;
currentorigin = [atcol, atrow];
currenttypenum = type;
currenttype = blockimages[type];
currentorientation = 0;
var i;
var block = blockimages[type];
var formula = blockformulas[type];
var imagenum;
var atc;
var atr;
for (i=0;i<=3;i++) {
atc = atcol + formula[i][0];
atr = atrow + formula[i][1];
imagenum=imagenumber(atc, atr);
//check for room to add block. If none, end game.
tests = String(document.images[imagenum].src);
found = tests.search("bblock.gif");
if (found>=0) {
document.images[imagenum].src = block;
current[i][0]=imagenum;
current[i][1] = atc;
current[i][2] = atr;
}
else {
alert("No room for new block. Game over.");
break;
}
}

**********************

tinytetris5
To test the game, but without playing the game, I make the down button actually move
the piece down one row. In what I term the real game, the down button is used to send
the piece as far down as it can go all in one step. This stage will check the internal action
to move the shape down until it hits something. This code works like the horizontal and
rotation operations. It is necessary to use a multi-step procedure with provision to restore
pieces if the move was blocked.
//move down one unit
function movedown() {
var i;
var tests;
var oksofar = true;
var imgno;
var atc;
var atr;
var newcurrent = new Array();
var saved = new Array();
var found;
for (i=0; i<=3; i++) {
imgno = current[i][0];

DRAFT 13
atc = current[i][1];
atr = current[i][2];

if (atr>=(vheight-1)) { //at very bottom already


//need to signal start of new block
alert("block i "+i+" is at bottom");
oksofar = false;
break;
}
newcurrent[i] = imagenumber(atc,atr+1);
}
if (oksofar) {
for (i=0;i<=3; i++) { //saved image nums & blank out current piece
saved[i] = current[i][0];
document.images[current[i][0]].src = "bblock.gif";
} // ends for loop
for (i=0; i<=3; i++) { //check if any blocking
tests = String(document.images[newcurrent[i]].src);
found = tests.search("bblock.gif");
if (found == -1) { // meaning it was not found
oksofar = false;
break;
} //ends if test
} //ends for loop
if (oksofar) {
for (i=0;i<=3; i++) {
document.images[newcurrent[i]].src = currenttype;
current[i][0] = newcurrent[i];
current[i][2]++; // y increases; x stays the same

} //ends for loop


currentorigin[1]++;
} //ends true clause for inner oksofar
else {
for (i=0;i<=3; i++) {
document.images[saved[i]].src = currenttype;

// signal need to start new falling piece


} //ends for loop
} //ends else of second oksofar
} //ends first if oksofar
}

************************

tinytetris6
In this stage, I added places to put lines and scores, but they are not yet used. This code
does do an examination of the lines, but it counts the blanks and not the filled ones. This
error is corrected in a later stage. The alert command is used to say what is found. Using
alert is a good way to make progress without having to do everything at once. However,
this function, completefalling, is invoked only if player attempts to move down after
actually hitting down. This is changed in the next stages.

DRAFT 14
//move down one unit
function movedown() {

if (atr>=(vheight-1)) { //at very bottom already
//need to signal start of new block
hitdown = true;
oksofar = false;
break;
}


if (oksofar) {
for (i=0;i<=3; i++) { //saved image nums & blank out current piece
saved[i] = current[i][0];
document.images[current[i][0]].src = "bblock.gif";
} // ends for loop
for (i=0; i<=3; i++) { //check if any blocking
tests = String(document.images[newcurrent[i]].src);
found = tests.search("bblock.gif");
if (found == -1) { // meaning it was not found
oksofar = false;
break;
} //ends if test
} //ends for loop
if (oksofar) {
for (i=0;i<=3; i++) {
document.images[newcurrent[i]].src = currenttype;
current[i][0] = newcurrent[i];
current[i][2]++; // y increases; x stays the same

} //ends for loop


currentorigin[1]++;
} //ends true clause for inner oksofar
else {
for (i=0;i<=3; i++) {
document.images[saved[i]].src = currenttype;
hitdown = true;
} //ends for loop
} //ends else of second oksofar
} //ends first if oksofar
if (hitdown) {
completefalling();
}
}
function completefalling() {
//check for completed lines
var i;
var j;
var imgno;
var blankcount;
var tests;
var found;
for (i=vheight-1;i>=0;i--) {
blankcount = 0;
for (j=hwidth-1;j>=0;j--) {
imgno = imagenumber(j,i);
tests = String(document.images[imgno].src);

DRAFT 15
found = tests.search("bblock.gif");
if (found>-1) { // a blank
blankcount++ ;
} //
}
alert("line i "+i+" has "+blankcount+" blanks");
}

//will signal next falling piece


}

**********************

tinytetris7

This stage corrects the last by adding the check to see if a piece has hit down and can't go
further using checkifhitdown function. The blanks are still being counted, and not the
non-blank spaces. I also made change to completefalling function to ease next step. I
added new hyperlinks buttons to allow starting different block types.

function checkifhitdown() { // but don't move it


var i;
var tests;
var oksofar = true;
var imgno;
var atc;
var atr;
var newcurrent = new Array();
var saved = new Array();
var found;
var hitdown = false;
for (i=0; i<=3; i++) {
imgno = current[i][0];
atc = current[i][1];
atr = current[i][2];

if (atr>=(vheight-1)) { //at very bottom already


//need to signal start of new block
hitdown = true;
oksofar = false;
break;
}
newcurrent[i] = imagenumber(atc,atr+1); //virtual move down
}
if (oksofar) {
for (i=0;i<=3; i++) { //save image nums & blank out current piece
saved[i] = current[i][0];
document.images[current[i][0]].src = "bblock.gif";
} // ends for loop
for (i=0; i<=3; i++) { //check if any blocking
tests = String(document.images[newcurrent[i]].src);
found = tests.search("bblock.gif");
if (found == -1) { // meaning it was not found

DRAFT 16
oksofar = false;
hitdown = true;
break;
} //ends if test
} //ends for loop
//restore blocks in all cases
for (i=0;i<=3; i++) {
document.images[saved[i]].src = currenttype;
} //ends for loop

} //ends first if oksofar


return hitdown;
}

//move down one unit


function movedown() {

} //ends first if oksofar


if (hitdown) {
completefalling();
}
else {
if (checkifhitdown()) { //tests if can go one more
completefalling(); }
}
}

function completefalling() {
//check for completed lines. Later add call for next piece to fall
var i;
var j;
var imgno;
var blankcount;
var tests;
var found;
i = vheight-1;
while (i>=0) {
blankcount = 0;
for (j=hwidth-1;j>=0;j--) {
imgno = imagenumber(j,i);
tests = String(document.images[imgno].src);
found = tests.search("bblock.gif");
if (found>-1) { // a blank
blankcount++ ;
} // end if test
}
alert("line i "+i+" has "+blankcount+" blanks");
i--;
} // end while loop of rows
} //end completefalling function

<a href="javascript:makeblock(1,5,1);">Make block 1 5 1 </a> <br/>


<a href="javascript:makeblock(3,2,1);">Make block 3 2 1 </a> <br/>

**************************

DRAFT 17
tinytetris8

This stage implements the task of removing filled lines and cascading the upper lines
down. The code counts spaces that are not blank and uses a variable, filledcount. There is
a variable named scoring used to give more points for removing multiple lines. My first
approach was to figure out how high up (high up on the board, low down in terms of
index values for the rows) the function needed to go. I made a variable called
lowestoccupiedrow. However, I abandoned this as requiring too much computation.
Instead, the for loop in the cascade function always goes all the way back to the 1st row.
The 0th row is blank because pieces start at row 1.

var scoring= [
1, 4, 8, 16]; // 1 for 1 line, 4 for 2 at a time, etc.

function completefalling() {
//check for completed lines. Later add call for next piece to fall
var i;
var j;
var imgno;
var filledcount;
var tests;
var found;
var linesremoved = 0;
i = vheight-1;

while (i>=0) {
filledcount = 0;
for (j=hwidth-1;j>=0;j--) {
imgno = imagenumber(j,i);
tests = String(document.images[imgno].src);
found = tests.search("bblock.gif");
if (found==-1) { // didn't find blank
filledcount++ ;
} // end if test
}

if (filledcount == hwidth) {
linesremoved++;
cascade(i);
//call cascade to remove line i. Will return here to while loop at new
line i
}
else {
i--;
}
} // end while loop of rows
if (linesremoved>0) {
document.f.lines.value = linesremoved +
parseInt(document.f.lines.value);
document.f.score.value = scoring[linesremoved-
1]+parseInt(document.f.score.value);

DRAFT 18
}
} //end completefalling function

function cascade(cut) {
// the line at row cut is to be removed, replaced by lines above
var upper;
var colindex;
var imgno;
var imgnox;
for (upper=cut;upper>0;upper--) {
for (colindex = 0; colindex<hwidth; colindex++) {
imgno = imagenumber(colindex,upper);
imgnox = imagenumber(colindex,upper-1);
document.images[imgno].src = document.images[imgnox].src;
}
}

}
</script>

<a href="javascript:makeblock(1,5,1);">Make block 1 5 1 </a> <br/>


<a href="javascript:makeblock(2,2,1);">Make block 2 2 1 </a> <br/>
<a href="javascript:makeblock(3,5,1);">Make block 3 5 1 </a> <br/>
<a href="javascript:makeblock(4,2,1);">Make block 4 2 1 </a> <br/>

**********************

tinytetris9

This next to the last stage was where I inserted the automatic start of a new block at the
top. This was made more elaborate at the last stage, when I finally put in timing.

function completefalling() {


if (filledcount == hwidth) {
linesremoved++;
cascade(i);
}
else {
i--;
}
} // end while loop of rows
if (linesremoved>0) {
document.f.lines.value = linesremoved +
parseInt(document.f.lines.value);
document.f.score.value = scoring[linesremoved-
1]+parseInt(document.f.score.value);
}

DRAFT 19
startnewpiece();
} //end completefalling function

function startnewpiece() {
var type = Math.floor(Math.random()*7);
var scol = Math.floor(Math.random()*5);
makeblock(type,scol,1); // start at second (index = 1)
row
}
function startgame() {
document.f.lines.value = "0";
document.f.score.value = "0";
startnewpiece();
}

*************************

tinytetris10

This last stage is when I added in the timing, that is, the automatic falling of the pieces.
My initial value for the interval was 2000 milliseconds, to give me time to think while
doing the debugging. At this point, I also decided to put in what I call a grace period.
After a piece hits blocks or the bottom of the board, there is a chance to make horizontal
moves before a new piece becomes the current piece. The 'real' game has this feature.
This involved setting up variables called startnewone and grace as well as the startgame
function. The approach appears to work, but I am not totally comfortable with it.
The startgame function invokes setInterval("clock();",timeperiod). The function clock
uses startnewone and grace. In some situations, it invokes makeblock and in others, it
invokes movedown. The completefalling function has changed. The display also has
changed, with the extra <a> javascript buttons removed.
Here is a table listing the functions with calling structure. This is a useful exercise to do
for applications. You can use the Find feature of NotePad or TextPad and then review to
decide if it makes sense. One question I asked myself was why moveover does not
require imagenumber. The answer is that the new image numbers can be calculated
directly as the originals plus dir, the parameter holding the direction. It may be possible
to extract common code from moveover, movedown and rotate since these do similar
things in preparing for moves.

Function Invoked by Calls


makeblock startnewpiece (calls clearInterval to turn
off calls to clock),
imagenumber
startnewpiece clock makeblock
moveover Buttons checkifhitdown

DRAFT 20
clock action set by call to startnewpiece, movedown,
setInterval completefalling
rotate Button checkifhitdown,
imagenumber
checkifhitdown movedown, rotate, imagenumber
moveover
movedown clock checkifhitdown,
imagenumber
completefalling clock cascade, imagenumber
startgame Hyperlink (calls setInterval which sets
up calls to clock)
cascade completefalling imagenumber
imagenumber multiple places
createboard called when HTML file
loaded

<html>
<head>
<title>Simple Tetris </title>
<script language="JavaScript">
var hwidth = 9; number of columns
var vheight = 15; number of rows
var tid; timer id
var timeperiod = 500; Used in call to setInterval to
set interval between drops. Make
longer and shorter to ease
debugging.
var grace = 0; default grace period
var startnewone = false; flag
var graceperiod = 3; grace period

function createboard() { called from script in body


var i;
var j;
for (i=0; i<vheight; i++) { 2-dimensional
for (j=0; j<hwidth; j++) {
document.write("<img write out html
src='bblock.gif'/>");
} close inner
document.write("<br/>"); make new row
} close outer
} close function

var blockformulas = [ initial construction of shapes


[[0,0],[1,0],[2,0],[1,1]], T shape
[[0,0],[1,0],[2,0],[3,0]], line
[[0,1],[1,1],[1,0],[2,0]], two two-block pieces shifted
[[0,0],[1,0],[0,1],[1,1]], brick
[[0,0],[1,0],[1,1],[2,1]], other shifted piece
[[0,0],[1,0],[2,0],[2,1]], opposite of L shape

DRAFT 21
[[0,1],[1,1],[2,1],[2,0]] L shape
];

var orientations = [ orientations[orient][type][block


0 to 3][x and y]
[ First element is blockformulas
[[0,0],[1,0],[2,0],[1,1]], // Note: // check off marks made
during testing
[[0,0],[1,0],[2,0],[3,0]], //
[[0,1],[1,1],[1,0],[2,0]], //
[[0,0],[1,0],[0,1],[1,1]], //
[[0,0],[1,0],[1,1],[2,1]], //
[[0,0],[1,0],[2,0],[2,1]], //
[[0,1],[1,1],[2,1],[2,0]] //
],
[ next orientation index = 1
[[1,0],[1,1],[1,2],[2,1]], //
[[1,0],[1,1],[1,2],[1,3]], //
[[1,2],[1,1],[0,1],[0,0]], //
[[0,0],[1,0],[0,1],[1,1]], //
[[1,0],[1,1],[0,1],[0,2]], //
[[1,2],[1,1],[1,0],[2,0]], //
[[2,2],[2,1],[2,0],[1,0]] //
],
[ next orientation index =2
[[0,1],[1,1],[2,1],[1,0]], //
[[0,0],[1,0],[2,0],[3,0]], //
[[2,0],[1,0],[1,1],[0,1]], //
[[0,0],[1,0],[0,1],[1,1]], //
[[0,0],[1,0],[1,1],[2,1]], //
[[2,1],[1,1],[0,1],[0,0]], //
[[2,0],[1,0],[0,0],[0,1]] //
],
[ next orientation index = 3
[[1,0],[1,1],[1,2],[0,1]], //
[[1,0],[1,1],[1,2],[1,3]], //
[[1,2],[1,1],[0,1],[0,0]], //
[[0,0],[1,0],[0,1],[1,1]], //
[[1,0],[1,1],[0,1],[0,2]], //
[[1,0],[1,1],[1,2],[0,2]], //
[[1,0],[1,1],[1,2],[2,2]] //
]
];
var scoring= [1, 4, 8, 16];
var blockimages = [ file names for single colored
blocks with borders
"darkblue.gif",
"lightblue.gif",
"green.gif",
"yellow.gif",
"red.gif",
"purple.gif",
"gray.gif"

DRAFT 22
];
var current = [ image number, column, row of
current 4- block shape
[0,0,0],
[0,0,0],
[0,0,0],
[0,0,0]
];
var currenttype; holds image file name
var currenttypenum; 0 to 6
var currentorientation; 0 to 3
var currentorigin; nominal origin [x,y]

function imagenumber(atcol, atrow) { generates the image tag number


from col and row
var imagenum = atrow*hwidth + atcol;
return imagenum;
}

function makeblock(type, atcol, atrow) { make a block of type type at


column atcol and at row atrow
var tests; used in testing if room
var found; used in testing if room
currentorigin = [atcol, atrow]; global var set here
currenttypenum = type; global var set here. type is a
number
currenttype = blockimages[type]; global var. It is the file name
currentorientation = 0; always start with 0 orientation.
This could be made random.
var i;
var block = blockimages[type]; Could be fixed to be currenttype
var formula = blockformulas[type]; Extract formula for this type
var imagenum; Used in loops
var atc; Used in loops for column
var atr; Used in loops for row
for (i=0;i<=3;i++) { Loop to build the 4-shape
atc = atcol + formula[i][0];
atr = atrow + formula[i][1];
imagenum=imagenumber(atc, atr);
//check for room to add block. If none, end
game.
tests = Make string from the src
String(document.images[imagenum].src);
found = tests.search("bblock.gif"); Look for file name indicating
blank
if (found>=0) { Okay to add new block
document.images[imagenum].src = block; Put it in appropriate value
for src (image file name)
current[i][0]=imagenum; Set initial data
current[i][1] = atc;
current[i][2] = atr;
}
else { Not okay
alert("No room for new block. Game Signal end of game

DRAFT 23
over.");
clearInterval(tid); Stop timing interval
break; Leave loop
} End of else (no room)
} End of loop
} End of function

function moveover(dir) { move left (-1) or right (1)


var i;
var tests; Used for test for possible block
var oksofar = true; Flag set to false if problem
var imgno;
var newcurrent = new Array(); Hold calculated new positions.
Will be array of 4 image
numbers.
var saved = new Array(); Hold image numbers of current
block. Used if restore necessary
for (i=0; i<=3; i++) { Loop to check edges & calculate
new positions.
imgno = current[i][0]; Image number of this block

if (dir==-1) { moving left


if (0 == imgno % hwidth) at left edge
{ oksofar = false;
break; } } End both if tests
if (dir == 1) { moving right
if ((hwidth-1)== imgno % hwidth) { at right edge
oksofar = false;
break; } } End both if tests
newcurrent[i] = imgno+dir; Simple adding of dir works
because not at edges
} End loop
// if oksofar (no blocks at critical edge,
newcurrent is set

if (oksofar) {
for (i=0; i<=3; i++) { Loop to setup saved
saved[i] = current[i][0];
document.images[current[i][0]].s Erase (blank out) current 4-
rc = "bblock.gif"; shape
} End for loop

for (i=0; i<=3; i++) { This for-loop will check for


conflicts
tests = Extract and make string
String(document.images[newcurrent[i]].src);
found = Search for indicator of blank
tests.search("bblock.gif");
if (found == -1) { If bblock.gif not found, then
something else was in this img
oksofar = false; problem (can't do move
break; break out of for loop
} End if clause
} End for loop

DRAFT 24
if (oksofar) { If [still] ok, do move
for (i=0;i<=3;i++) { for loop
document.images[newcurrent[i] Move in this image file
].src= currenttype;
current[i][0] = Set new values—for image
newcurrent[i]; number
current[i][1] = Change the column value (row
current[i][1]+dir; stays the same)
} End loop

currentorigin[0]=currentorigin[ Set current origin value


0]+dir;
checkifhitdown(); Check if this means piece cannot
go down (slipped under/into
place)
} End if oksofar
else { Need to restore into saved
images
for (i=0;i<=3;i++) { for loop
document.images[saved[i]].src = currenttype;
} End for loop
} End else
} End outer if okaysofar
} End function

function rotate() { rotate current piece


var block = currenttype;
var savedorientation = currentorientation; May need to back up if this
orientation clashes with other
pieces.
currentorientation = (currentorientation+1) % 4; rotates to next orientation.
Uses modulus to go from 3 to 0.
var i;
var formula = Pick up formula
orientations[currentorientation][currenttypenum];
var atcol = currentorigin[0];
var atrow = currentorigin[1];
var atc;
var atr;
var tests;
var newcurrent = Array(); Calculated new img
var saved = Array(); Used in case need to restore
var oksofar = true; flag
Calculate new imagenumbers & chk
if over right side. Also need to
check if over left side
for (i=0;i<=3;i++) { For loop for initial step
atc = atcol + formula[i][0]; Determine new column
if (atc>=(hwidth-1)) { Over the right edge?
oksofar = false;
break; } Leave for loop. End clause.
if (atc<0) { Over the left edge?
oksofar = false;
break; } Leave for loop. End clause.
atr = atrow + formula[i][1]; Determine new row

DRAFT 25
if (atr>=(vheight-1)) { Past the bottom of board?
oksofar = false;
break; } Leave loop. End clause.
newcurrent[i]=imagenumber(atc, atr); Calculate new img number.
} End for loop
if (oksofar) { If no problem so far…
for (i=0;i<=3;i++) { Save img numbers & clear slots
saved[i] = current[i][0];
document.images[current[i][0]].src =
"bblock.gif" }
for (i=0;i<=3;i++) { now go through and check each
target slot for block: for loop
tests = Prepare to check for clashes
String(document.images[newcurrent[i]].src);
found = tests.search("bblock.gif");
if (found == -1) { Something else in src
oksofar = false;
break; } End clause
} End for loop
if (oksofar) { If ok…no clashes
for (i=0;i<=3;i++) { For loop: do the move
imagenum=newcurrent[i];
document.images[imagenum].src = block;
current[i][0]=imagenum; Set new current data
current[i][1] = atcol+formula[i][0];
current[i][2] = atrow+formula[i][1];
}
checkifhitdown(); may have hit bottom as result of
rotate
} End if okay
else { need to restore from saved
for (i=0;i<=3;i++) { for loop
document.images[saved[i]].src = block;
} End for loop
currentorientation = savedorientation; Restore old orientation
} End else clause
} close first if oksofar
else { Else clause for first if
okaysofar
currentorientation = savedorientation; Restore old orientation
} End clause
} close function
function checkifhitdown() { Check if piece can't move
further down (no move). Similar
to code in move functions
var i;
var tests;
var oksofar = true;
var imgno;
var atc;
var atr;
var newcurrent = new Array();
var saved = new Array();
var found;

DRAFT 26
var hitdown = false;
for (i=0; i<=3; i++) {
imgno = current[i][0];
atc = current[i][1];
atr = current[i][2];
if (atr>=(vheight-1)) { at very bottom already
hitdown = true;
oksofar = false;
break;
}
newcurrent[i] = imagenumber(atc,atr+1); virtual move down
}
if (oksofar) {
for (i=0;i<=3; i++) { save image nums & blank out
current piece
saved[i] = current[i][0];
document.images[current[i][0]].src =
"bblock.gif";
} // ends for loop
for (i=0; i<=3; i++) { check if any blocking
tests =
String(document.images[newcurrent[i]].src);
found =
tests.search("bblock.gif");
if (found == -1) { meaning it was not found
oksofar = false;
atc = currentorigin[1];
hitdown = true;
break;
} ends if test
} ends for loop
for (i=0;i<=3; i++) { restore blocks in all cases
document.images[saved[i]].src =
currenttype;
} ends for loop
} ends first if oksofar
startnewone = true; Flag to start new piece, but…
grace = graceperiod; … will allow grace period (3
intervals)
return hitdown; This function returns value
} End function

function movedown() { move down one unit


var i; index variable
var tests; Used in search test of src
var oksofar = true; Flag
var imgno; Will hold imgno (for images
collection)
var atc; Column
var atr; Row
var newcurrent = new Array(); Img numbers following move
var saved = new Array(); To save img numbers if move
causes conflict
var found; Flag

DRAFT 27
var hitdown = false; Initialize to false
for (i=0; i<=3; i++) { For loop
imgno = current[i][0]; Img number for this block
atc = current[i][1]; Column of this block
atr = current[i][2]; Row of this block
if (atr>=(vheight-1)) { at very bottom already
hitdown = true; Flag
oksofar = false; Flag
break; Leave for loop
} End if clause
newcurrent[i] = imagenumber(atc,atr+1); Set newcurrent (used later to
make the move)
} End for loop
if (oksofar) { No problems so far
for (i=0;i<=3; i++) { save image nums & blank out
current piece
saved[i] = current[i][0]; just in case
document.images[current[i][0]].src = put in blank gif
"bblock.gif";
} ends for loop
for (i=0; i<=3; i++) { Now can check for absence of
other pieces
tests = Extract src
String(document.images[newcurrent[i]].src);
found = Do search
tests.search("bblock.gif");
if (found == -1) { meaning it was not found
oksofar = false; Problem—other piece
break; Leave for loop
} ends if test
} ends for loop
if (oksofar) { No problems
for (i=0;i<=3; i++) { For loop
document.images[newcurrent[i]].src = Do the move
currenttype;
current[i][0] = newcurrent[i]; Set current data
current[i][2]++; y increases; x stays the same
} //ends for loop
currentorigin[1]++;
} ends clause for inner oksofar
else { Else for problem
for (i=0;i<=3; i++) { for loop
document.images[saved[i]].src = Restore current image
currenttype;
hitdown = true; Set flag indicating hitdown
} ends for loop
} ends else of second oksofar
} ends first if oksofar
if (hitdown) { tried to move down beyond
startnewone=true; Set flag to start new piece
grace = 0; No grace period
} End if clause
else { Not down now, but
if (checkifhitdown()) { tests if can go one more

DRAFT 28
startnewone = true; Set flag to start new piece
grace = graceperiod; Allow grace period
} End if
} End else
} End function
function clock () { Called by setInterval
if (startnewone) { Start new piece after any grace
period
if (grace==0) { Check grace
startnewone = false; reset flag
completefalling(); call function to check for
filled lines
startnewpiece(); Call function to start new
piece
} End if grace down to zero
else { Still grace period
grace--; } Decrement grace
} End if startnewone
movedown(); //move current piece down In all cases, move piece down
} End function

function completefalling() { check for completed lines.


var i; Index variables
var j;
var imgno;
var filledcount; Used in counting up blocks
var tests; Used in testing
var found;
var linesremoved = 0; For scoring
i = vheight-1; Start from bottom
while (i>=0) { Go to top
filledcount = 0; Initialize for each row
for (j=hwidth-1;j>=0;j--) { Inner loop—along columns
imgno = imagenumber(j,i); compute img number
tests = Extract src
String(document.images[imgno].src);
found = Search for blank
tests.search("bblock.gif");
if (found==-1) { didn't find blank
filledcount++ ; increment filledcount
} end if test
} End inner for loop
if (filledcount == hwidth) { Is row all filled?
linesremoved++; one more line to remove
cascade(i); Call cascade function to do
it. Will return to do this line
again.
} End if test
else { Row not filled so…
i--; back up to previous line
} End else clause
} end while loop of rows
if (linesremoved>0) { Any lines removed?
document.f.lines.value = linesremoved + Increment displayed count

DRAFT 29
parseInt(document.f.lines.value);
document.f.score.value = Increase displayed score using
scoring[linesremoved- scoring values
1]+parseInt(document.f.score.value);
} End if test for lines removed
} end completefalling function

function cascade(cut) { the line at row cut is to be


removed, replaced by lines above
var upper; Index value for (upper) rows
var colindex; Index for columns
var imgno; Img number for target img
var imgnox; img number for source
for (upper=cut;upper>0;upper--) { For loop starting from cut
for (colindex = 0; colindex<hwidth; Inner for loop to do columns
colindex++) {
imgno = imagenumber(colindex,upper); Calculate target
imgnox = imagenumber(colindex,upper-1); Calculate source (right above)
document.images[imgno].src = Move image down
document.images[imgnox].src;
} End inner loop
} End outer loop
} End cascade function

function startnewpiece() { Starts new falling piece


var type = Math.floor(Math.random()*7); Generate random choice of type
var scol = Math.floor(Math.random()*5); Generate random column
makeblock(type,scol,1); Invoke makeblock. Always at 1st
row (leaving 0th row to
cascade).
} End function
function startgame() { Start game.
document.f.lines.value = "0"; Set visible scores to zero
document.f.score.value = "0";
startnewone=true; Set flag.
grace = 0; Set grace period to zero.
tid = setInterval("clock();",timeperiod); Start intervals.
} End of startgame. Called by
hyperlink.
</script>
</head>
<body>
<table> Table to layout board and
buttons
<td>
<table border="1">
<td>
<script language="JavaScript"> Internal javascript to call …
createboard(); function to create board
</script>
</td>
</table>
</td>
<td>
<form name="f"> Form for

DRAFT 30
<input type="button" onClick="moveover(-1);" buttons…
value="left">
<input type="button" onClick="rotate();"
value="rotate">
<input type="button" onClick="moveover(1);"
value="right">
<br/>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
<input type="button" onClick="movedown();" Does work, but should be changed
value="down"> to do move all the way down
<br/>
Lines: <input type="text" name="lines" value="0"> displayed lines removed
<br/>
Score: <input type="text" name="score" value="0"> displayed score
<br/>
</form>
</br>

</td>
</table>
<img src="bblock.gif"/>
<a href="javascript:startgame();">Start Game </a> Hyperlink to call startgame
<br/> function

</body>
</html>

DRAFT 31

You might also like