In this Active Premium tutorial we'll be creating a variant of the classic Tetris game, explaining general game concepts as well as some specific to this genre.
Test the demo above, but don't play it for too long - you'll soon find out that making it can be as fun as playing it.
Introduction
Let's start with a short history lesson for those who are not familiar with this game (even though I doubt that it's an unknown game to anyone).
Tetris is a puzzle video game from the 80s in which pieces made out of four square units (or cells) fall down in a grid (or matrix) and you have to move and rotate them until they land so that you obtain horizontal lines of units without gaps.
The pieces in the game are called tetrominoes and are geometrical shapes composed of four squares. There are five unique tetrominoes; however in the Tetris game we use two more which are reflections of two pieces from the first five.
The game was designed and programmed by Alexey Pajitnov and released in 1984 and still remains a popular game although many variations of the game were created (but using the same concept).
To read more on Tetris game, Tetrominoes, and associated topics, you can read their wiki pages: Tetris and Tetromino.
Prerequisites
For this tutorial I'll be using FlashDevelop (see a guide to installing and using that here) but you can use whatever software you like (Flash CS3+, Flash Builder, FDT). If you don't like or you can't get FlashDevelop for whatever reason I strongly recommend that you use something other than Flash because of the weak code editor it has and because we will only be writing classes and you'll like the features that you find in a strong code editor. But if you'll only be copying and pasting Flash will do just fine.
Step 1: Creating the Main Game Class
First of all let's create the main class of our game. For now it will only be an empty class that we'll update along the way.
package com.tutsplus.active.tetris { import flash.display.Sprite; public class Tetris extends Sprite { public function Tetris() { } } }
Name the class Tetris
and make it extend the Sprite
class.
Note: If you are using Flash Professional this could be set as the document class. It could alternatively be instantiated as a simple display object on the timeline or in a class:
var game:Tetris = new Tetris(); addChild(game);
If you are using FlashDevelop and want to use this class as the main class you can right-click on it in the Project panel and select Always Compile from the context menu.

Step 2: Creating the Unit Class
The Unit
object is the primary object used in our game, and represents a single "block" within a tetromino. The Tetris playfield is a two dimensional array which holds references to Unit
objects representing the fallen tetrominoes. Create a new class and set its name to Unit
.
package com.tutsplus.active.tetris.data { public class Unit { private var _x:int, _y:int; private var _type:String; public function Unit(x:int, y:int) { _x = x; _y = y; } } }
The Unit object will be characterized by two basic properties: the x
and y
position in the grid. These are both integers. The _type
variable will be used to identify the type of the Tetromino in which the unit resides. We'll discuss Tetrominoes and their types in later steps.
Step 3: Adding Getters and Setters for the Properties
Because the _x
and _y
properties are both declared as private we need a way to access them from outside the class. This can be accomplished by adding getters and setters:
public function set x(value:int):void { _x = value; } public function set y(value:int):void { _y = value; } public function set type(value:String):void { _type = value; } public function get x():int { return _x; } public function get y():int { return _y; } public function get type():String { return _type; }
You may be wondering why go through all this trouble just for two properties. To be short, we need to control the modifications of the coordinates as you will see later on.
Step 4: Creating the Unit's Visual Representation
Okay. Up until now we have nothing to see so let's create a visual representation of a Unit
object. This will be represented by the Box
class:
package com.tutsplus.active.tetris.display { import flash.display.Sprite; public class Box extends Sprite { private var _color:uint; public static const SIZE:uint = 30; public function Box(color:uint) { } } }
As you can see the Box
class has two properties, one which is private and holds the color of the box, and one which is public called SIZE
. The last one is a constant and it's static
because it's common to all Box
objects and we'll need to use it later on when creating the grid. You can consider this as a scale of a unit's size (which is 1x1, so our units will, eventually, be 30x30px).
Step 5: Drawing the Box
Right now the Box
class does nothing. Add the following lines to the constructor of the class:
_color = color; draw(); addEffects();
As you can see we set the _color property to the constructor's color parameter and we call two methods named draw()
and addEffects()
so we must create them too.
The draw() method:
private function draw():void { graphics.clear(); graphics.lineStyle(1, 0x006699, 0.9, true, "none") graphics.beginFill(_color); graphics.drawRect(1, 1, SIZE-2, SIZE-2); graphics.endFill(); }
Here we basically draw a 30x30 rectangle with 1 pixel border. We use the SIZE
static constant to set the size of the rectangle.
The addEffects() method:
private function addEffects():void { var bf:BevelFilter = new BevelFilter(4, 45, 0xffffff, 0.4, 0x000000, 0.5, 2, 2, 10, 10); this.filters = [bf]; }
In this method we create a BevelFilter
and add it as a filter for our box. For this to work you will need to import the BevelFilter
class also:
import flash.filters.BevelFilter;
Step 6: Linking the Display to the Unit
Now that we have a visual representation ready, go back into the Unit
class and add a new private variable _display
of type Box
:
private var _display:Box;
Also add a new public method called toDisplay()
in which we check whether an Unit
object has a visual representation and create a new one if not.
public function toDisplay():Box { _display = _display ? _display : new Box(0xff0000); _display.x = x * Box.SIZE; _display.y = y * Box.SIZE; return _display; }
To see the result add the following lines of code in the Tetris
class:
First add a new private variable _u
private var _u:Unit;
Then add the following lines of code in the constructor of the class:
_u = new Unit(0, 0); addChild(_u.toDisplay());
A red beveled rectangle should appear on the stage.
Note: I've used the following line of code to set the stage size, color and frame rate:
You can add it just below the import statements in the Tetris
class.
[SWF(width = "550", height = "600", frameRate = "31", backgroundColor = "0x000912")]
Milestone 1
Step 7: Moving the Created Unit
Now that we have a something displayed in the screen let's go ahead and move it. In the Tetris
class add a new private variable _timer
of type Timer
, attach an event listener to it and call the start()
method:
package com.tutsplus.active.tetris { import com.tutsplus.active.tetris.data.Unit; import flash.display.Sprite; import flash.events.TimerEvent; import flash.utils.Timer; public class Tetris extends Sprite { private var _timer:Timer = new Timer(700); private var _u:Unit; public function Tetris() { _u = new Unit(0, 0); addChild(_u.toDisplay()); _timer.addEventListener(TimerEvent.TIMER, onTimer); _timer.start() } } }
Also create the onTimer()
event handler function; in this we will increment the y
property of the Unit
object created earlier:
private function onTimer(e:TimerEvent):void { _u.y += 1; }
This should result in moving the box on the screen by one unit. However if you test the movie now the box will stand still. If you didn't notice we set the y
property of the Unit
object which is not a display object. We have to update the position of the _display
object in the Unit
class which is the actual visual representation on the screen.
Step 8: Updating the Unit's Display
To update the display of the unit we need to update the x
and y
setters as show below:
public function set x(value:int):void { _x = value; if(_display) _display.x = _x * Box.SIZE; } public function set y(value:int):void { _y = value; if(_display) _display.y = _y * Box.SIZE; }
This will update the position of the Box
object by a number of pixels equal to its size.
If you test the movie now you should get the same result as me: the box drops down each 700ms by 30 pixels.
Milestone 2
Refresh the page if the above SWF is entirely black.)
Step 9: The Tetromino
Okay, now that we have a unit and we can move it we will create the larger piece made out of four units. Before we actually start doing that we need to discuss the Tetromino object.
A Tetromino is a cluster of four units orthogonally attached. There are a total of seven Tetrominoes in the game although only five of them are unique combinations of units.
The seven shapes are as follow:
- The I shape
- The J shape
- The L shape
- The O shape
- The S shape
- The Z shape
- The T shape

Step 10: The TetrominoType Class
To have a quick reference to the seven types of Tetrominoes we'll create a TetrominoType
class which will store all of them and give us access to them by the representing letter.
package com.tutsplus.active.tetris.data { public class TetrominoType { private var _type:String; public static const I:String = 'I'; public static const J:String = 'J'; public static const L:String = 'L'; public static const O:String = 'O'; public static const S:String = 'S'; public static const Z:String = 'Z'; public static const T:String = 'T'; public function TetrominoType(type:String) { this.type = type; } public function set type(value:String):void { _type = value; } public function get type():String { return _type; } public function toString():String { return _type; } } }
In this class we currently have added some public static constants which represent a Tetromino type, a private variable which represents the type of the TetrominoType
object and the getter and setter for the _type
private property. Also there is a toString()
method which returns a string representation of the TetrominoType
object.
Next I've added a isValidType(type)
method to check whether the passed type is a valid one. This is not strictly necessary but it's good to have the highest degree of control for every possible error source.
public static function isValidType(type:String):Boolean { switch(type.toUpperCase()) { case I: case J: case L: case O: case S: case Z: case T: return true; break; default: return false; } }
Note: If you're not familiar with the multiple case statements without a break
keyword, this is used to test multiple values and execute the same command for them all.
Also you need to update the type
setter to check whether the type is valid and throw an error if not:
public function set type(value:String):void { if (isValidType(value)) _type = value; else throw new Error("Invalid tetromino type specified."); }
Step 11: The Tetromino Class
Now that we have created the TetrominoType
class for better control over the type of a Tetromino piece let's create the actual object.
package com.tutsplus.active.tetris.data { public class Tetromino { private var _unit1:Unit; private var _unit2:Unit; private var _unit3:Unit; private var _unit4:Unit; private var _type:String; public function Tetromino(type:String = '') { if(TetrominoType.isValidType(type)) _type = type; } public function get unit1():Unit { return _unit1; } public function get unit2():Unit { return _unit2; } public function get unit3():Unit { return _unit3; } public function get unit4():Unit { return _unit4; } public function get type():String { return _type; } } }
As said before a Tetromino is made out of four units in seven possible combinations so we need to add four private variables representing the units and a private variable holding the type of the Tetromino object.
The getters are used to access each unit of a Tetromino later on.
Step 12: Generating Each Shape
Next we need to group the units based on the specified type.
private function generateGroup():void { switch(_type) { case TetrominoType.I: createI(); break; case TetrominoType.J: createJ(); break; case TetrominoType.L: createL(); break; case TetrominoType.O: createO(); break; case TetrominoType.S: createS(); break; case TetrominoType.Z: createZ(); break; case TetrominoType.T: createT(); break; default: throw new Error("Invalid tetromino type"); } }
Note: Don't forget to add a call to the generateGroup()
method in the constructor of the class just after assigning the type
public function Tetromino(type:String = '') { if (TetrominoType.isValidType(type)) _type = type; generateGroup(); }
The generateGroup()
method uses a switch-case
statement to create the shape based on the type. Each type is constructed in its own method so we need to write them next:
private function createI():void { _unit1 = new Unit(0, 0); _unit2 = new Unit(1, 0); _unit3 = new Unit(2, 0); _unit4 = new Unit(3, 0); _unit1.type = unit2.type = unit3.type = unit4.type = type; } private function createJ():void { _unit1 = new Unit(0, 0); _unit2 = new Unit(0, 1); _unit3 = new Unit(1, 1); _unit4 = new Unit(2, 1); _unit1.type = unit2.type = unit3.type = unit4.type = type; } private function createL():void { _unit1 = new Unit(2, 0); _unit2 = new Unit(0, 1); _unit3 = new Unit(1, 1); _unit4 = new Unit(2, 1); _unit1.type = unit2.type = unit3.type = unit4.type = type; } private function createO():void { _unit1 = new Unit(0, 0); _unit2 = new Unit(1, 0); _unit3 = new Unit(0, 1); _unit4 = new Unit(1, 1); _unit1.type = unit2.type = unit3.type = unit4.type = type; } private function createS():void { _unit1 = new Unit(1, 0); _unit2 = new Unit(2, 0); _unit3 = new Unit(1, 1); _unit4 = new Unit(0, 1); _unit1.type = unit2.type = unit3.type = unit4.type = type; } private function createZ():void { _unit1 = new Unit(0, 0); _unit2 = new Unit(1, 0); _unit3 = new Unit(1, 1); _unit4 = new Unit(2, 1); _unit1.type = unit2.type = unit3.type = unit4.type = type; } private function createT():void { _unit1 = new Unit(1, 0); _unit2 = new Unit(0, 1); _unit3 = new Unit(1, 1); _unit4 = new Unit(2, 1); _unit1.type = unit2.type = unit3.type = unit4.type = type; }
In each creation method we assign a new unit of some given coordinates for each unit variable in the class and set the types of those units as the type of the created tetromino.
Step 13: Explaining the Coordinates
To better understand how we assigned the coordinates to each unit let's take a look at the following drawing made for the J shape:

Here we have a 2D coordinate system where the X axis positive direction is towards right and the Y axis positive direction is downwards. A single square represents a unit in the coordinate system and the numbers along each axis represent coordinate values for each square.
The white numbers inside each filled square represent the unit number.
So if you look at this example you'll see that the J shape has the following coordinates for its units:
- Unit 1: X = 0, Y = 0
- Unit 2: X = 0, Y = 1
- Unit 3: X = 1, Y = 1
- Unit 4: X = 2, Y = 1
The same goes for the rest of the Tetrominoes. You can use the previous drawing to check the coordinates for each Tetromino.
Note: Each unit in a Tetromino is numbered from left to right on each line.
Step 14: Adding and Moving the Tetromino
Now that we have our piece let's see how to create it and move it. Make the following modifications to the Tetris
class:
package com.tutsplus.active.tetris { import com.tutsplus.active.tetris.data.Tetromino; import com.tutsplus.active.tetris.data.Unit; import flash.display.Sprite; import flash.events.TimerEvent; import flash.utils.Timer; public class Tetris extends Sprite { private var _timer:Timer = new Timer(700); private var _t:Tetromino; public function Tetris() { _t = new Tetromino("J"); addChild(_t.unit1.toDisplay()); addChild(_t.unit2.toDisplay()); addChild(_t.unit3.toDisplay()); addChild(_t.unit4.toDisplay()); _timer.addEventListener(TimerEvent.TIMER, onTimer); _timer.start() } private function onTimer(e:TimerEvent):void { _t.unit1.y += 1; _t.unit2.y += 1; _t.unit3.y += 1; _t.unit4.y += 1; } } }
In here we have replaced the _u
variable with the _t
variable of type Tetromino
. After creating the Tetromino we've added to the display list the visual representation of each unit in the piece. In the event handler for the TimerEvent
we're incrementing the y
coordinate for each unit in the Tetromino.
If you test the code now you should see a J tetromino falling down.
Milestone 3
(Again, refresh the page if the blocks have already fallen off the bottom of the SWF.)
Step 15: Getting a Random Shape
Right now each time we compile we will get only a J shape which is not really the only shape. We need to fix this as we want to get a random shape each time one it's created.
This can be done easily from the TetrominoType
class but before going further let's take a look at the method of randomization and its rules.
I've used an implementation of Tetris's Random Generator which is briefly discussed here. To keep it short the rules say that the first spawned Tetromino should not be O, S or Z. After the first piece the game must pick some pieces and if there were recently spawned to try again for a limited amount of tries.
To achieve what we need we'll use an array holding the four most recently spawned shapes. Add the following line of code into the TetrominoType
class:
private static var _history:Array = [S, Z, O, ''];
Step 16: Randomization Method
Now that we've added an array representing the history of spawned types we'll create a public static getter which will be used to return a random type:
public static function get random():String { var t:String; for (var i:int = 0; i < 4; i++) { t = ALL[randomPosition]; if (isRecent(t)) continue; else break; } addToHistory(t); return t; }
In here a for loop is used to get a random from the ALL[]
array and test if it has been recently created.
Notice that we've used a property called randomPosition
which gets a random index from 0 to 6.
private static function get randomPosition():uint { return Math.round(Math.random() * 6); }
Also there are two more methods: the isRecent()
method which tests if the current type exists in the history
private static function isRecent(type:String):Boolean { return _history.indexOf(type) != -1; }
and the addToHistory()
method which adds the selected type to the recently spawned types array.
private static function addToHistory(type:String):void { _history.pop(); _history.unshift(type); }
Last but not least we need to modify the Tetromino
class so that a random type gets selected. To do this open the Tetromino
class and change the following line from the constructor
public function Tetromino(type:String = '') { if (TetrominoType.isValidType(type)) _type = type; generateGroup(); }
into
public function Tetromino(type:String = "") { _type = TetrominoType.isValidType(type) ? type : TetrominoType.random; generateGroup(); }
Now before testing we need to change one more thing. If you haven't noticed the color of a unit is always red and we'll want to have certain color for each tetromino type so let's fix that.
Step 17: Colors of Tetrominoes
For this tutorial I've chosen the following colors which are based on The Tetris Company standardization but feel free to change them to whatever you want:
- The I shape: cyan (0x00FFFF)
- The J shape: blue (0x0000FF)
- The L shape: orange(0xFF8040)
- The O shape: yellow (0xFFFF00)
- The S shape: green (0x00FF00)
- The T shape: purple (0x800080)
- The Z shape: red (0xFF0000)
After you have settled on what colors you'll use go ahead and create a class to hold this data. We'll call the class TetrominoColor
.
Step 18: The TetrominoColor Class
Create a new class, name it TetrominoColor
and add a private variable in it called _color
of type uint
.
package com.tutsplus.active.tetris.data { public class TetrominoColor { private var _color:uint; public function TetrominoColor(type:String) { _color = getColor(type); } public static function getColor(type:String):uint { } } }
The type
type parameter represents the type of the tetromino.
Also there's a getColor()
method which is used to return the color corresponding to the specified type.
In the getColor()
method we'll use a switch-case statement to get the right color code.
public static function getColor(type:String):uint { var color:uint; switch(type) { case TetrominoType.I: color = 0x00ffff; break; case TetrominoType.J: color = 0x0000ff; break; case TetrominoType.L: color = 0xff8040; break; case TetrominoType.O: color = 0xffff00; break; case TetrominoType.S: color = 0x00ff00; break; case TetrominoType.T: color = 0x800080; break; case TetrominoType.Z: color = 0xff0000; break; default: throw new Error("Invalid tetromino type"); } return color; }
And to finish the class let's add two more methods (toString()
and toUint
) to return the color from the TetrominoColor
object as a string and as an unsigned integer.
public function toString():String { return String(_color); } public function toUint():uint { return _color; }
Step 19: Random Tetromino of Corresponding Color
Now that we have modified the TetrominoType
class to generate a random tetromino each time and we've created the TetrominoColor
class to give us the color corresponding to each tetromino type,, we'll make some small modifications to make things work properly.
First of all in the Tetris
class change the following line from the constructor:
_t = new Tetromino("J");
into
_t = new Tetromino();
This will generate a random tetromino each time when we compile instead of creating a specific type.
The second thing we need to do is change the color of the Box
object in the toDisplay()
method of the Unit
. Right now the color has a constant value of red (0xff0000). To get the corresponding color change the line
_display = _display ? _display : new Box(0xff0000);
into
_display = _display ? _display : new Box(TetrominoColor.getColor(type));
If you test the code now you should get a random tetromino with its associated color each time when you compile the project.
Milestone 4
Step 20: Adding "Gravity"
Okay. Up until now everything works just fine, but using just a timer to move our pieces doesn't give us much control. What we need is a class to simulate some kind of gravity in which we can register pieces to be affected by it, control the falling speed, be able to start and stop the dropping and also to unregister pieces so that they're no longer affected by it.
To do everything I've enumerated we'll make use of a class called Gravity
which will encapsulate all these features.
package com.tutsplus.active.tetris.dynamics { import flash.events.TimerEvent; import flash.utils.Timer; public class Gravity { private var _velocity:uint; private var _timer:Timer; public function Gravity() { _timer = new Timer(850 - 100 * _velocity); } } }
As you can see in the Gravity
class we still use a Timer
to control the gravity step, but we also use a _velocity
variable which will be increased with the level and that will change the timer delay so that pieces will fall faster and faster.
First of all let's add a getter and setter for the velocity.
public function set velocity(value:uint):void { if (value < 9) _velocity = value; _timer.delay = 850 - 100 * _velocity; } public function get velocity():uint { return _velocity; }
Step 21: Registering and Unregistering Actions
Now that our class is set we need to add some functionality which allows us to register and unregister certain actions that will be performed at each timer tick.
For this we'll use the register(action)
and unregister(action)
methods in which action
represents the function to be executed (this will be used as an event handler for TimerEvent
events).
public function register(action:Function):void { _timer.addEventListener(TimerEvent.TIMER, action); } public function unregister(action:Function):void { _timer.removeEventListener(TimerEvent.TIMER, action); }
Step 22: Starting and Stopping
Finally we need a way to control the falling as in stopping and starting it.
public function start():void { _timer.start(); } public function stop():void { _timer.stop() }
To start or stop the gravity we simply call the start()
or stop()
methods on our timer. With this our Gravity
class is complete.
Step 23: Testing the Gravity
Now that we have a Gravity
class let's make a test run. Back in the Tetris
class remove the _timer
variable and rename the onTimer()
event handler into moveDown()
and pass in null
as the default parameter.
ppackage com.tutsplus.active.tetris { import com.tutsplus.active.tetris.data.Tetromino; import com.tutsplus.active.tetris.dynamics.Gravity; import flash.display.Sprite; import flash.events.TimerEvent; [SWF(width = "550", height = "600", frameRate = "31", backgroundColor = "0x000912")] public class Tetris extends Sprite { private var _g:Gravity; private var _t:Tetromino; public function Tetris() { _t = new Tetromino(); addChild(_t.unit1.toDisplay()); addChild(_t.unit2.toDisplay()); addChild(_t.unit3.toDisplay()); addChild(_t.unit4.toDisplay()); _g = new Gravity(); _g.register(moveDown); _g.start(); } private function moveDown(e:TimerEvent):void { _t.unit1.y += 1; _t.unit2.y += 1; _t.unit3.y += 1; _t.unit4.y += 1; } } }
After that add the highlighted lines into the constructor and test the movie. The obtained result should be the same as the previous one. The only difference is that we've separated the gravity logic from the main class.
Milestone 5
Step 24: Increasing Control
So if we look over what we've accomplished up to now we can see that our game is taking shape -- but it's still far from complete. The piece we spawn is currently added right in the display list and we have no direct way of controlling the position of its units.
The Tetris playboard is composed out of a grid (well, matrix) of 20 lines and 10 columns in which the moving piece can move. To add this bounds we will use a two dimensional array (or matrix in which we'll store the units of the landed pieces.
package com.tutsplus.active.tetris.data { public class Grid { private var _lines:uint; private var _columns:uint; private var _grid:Array; public function Grid(columns:uint, lines:uint) { _lines = lines; _columns = columns; createGrid(); } private function createGrid():void { _grid = []; for (var i:uint = 0; i < _lines; i++) _grid[i] = new Array(_columns); } public function getUnitAt(x:int, y:int):Unit { return _grid[y][x]; } public function get lines():uint { return _lines; } public function get columns():uint { return _columns; } } }
In this class we have three private variables. The first two, _lines
and _columns
, obviously hold the number of lines and columns of the grid, and the third one is an array which will hold every element from the grid.
The constructor accepts two parameters representing the number of columns and lines, as you may want to have more (or less) than 10 columns or 20 lines.
The createGrid()
method creates the two dimensional array (or matrix) by creating an array with the same number of elements as the number of columns (representing one line) and adding it to each element in the _grid
array which obviously has the same length as the number of lines.
Beside the getters for the number of lines and columns we also have a method called getUnitAt(x, y)
to return a unit from the grid at a specified position.
Step 25
The only thing we need to have to do now is add a couple of methods: one to check whether a unit exists at a specified place in the grid and one to set a unit in the grid at its location. The isUnitAt(x, y)
and setUnit(unit)
methods do what we need.
public function isUnitAt(x:int, y:int):Boolean { if (y < 0) return false; else if (x >= 0 && x < columns && y < _lines) return _grid[y][x] != null; else return true; } public function setUnit(unit:Unit):void { if(unit.x >= 0 && unit.x < _columns && unit.y >= 0 && unit.y < _lines) _grid[unit.y][unit.x] = unit; }
In the isUnitAt()
method first checks whether the y
coordinate of the unit is negative (meaning that the unit is outside the grid being freshly spawned), after which it checks whether the x
and y
coordinates are contained by the grid and returns true
if that location is occupied or false
otherwise. Lastly if the other conditions are not fulfilled it means the specified position is outside the grid (meaning left, right or under the grid -- places where a unit can't be).
Also add another method to the class which will clear all elements in the grid.
public function clearLines():void { for (var i:int = 0; i < _lines; i++) _grid[i] = new Array(_columns); }
We'll use this method much later in the tutorial when we'll want to stop the game.
Step 26: Discussing Movement
Now that we have a grid to contain our pieces and have defined some bounds for it we need to further extend our game by adding a class to control the left, right, down movement and the rotation of a piece.
And so we'll create the MotionManager
class but before we go there let's discuss how things are moved in tetris.
Left, right and down movements are obvious. A piece can only move left or right if none of its units is obstructed by a wall or another unit. The same goes for down movements, the only difference being that when the pieces hit the ground or are on top of another piece these are landed and cannot be moved further, another piece is spawned.
Rotations are a bit tricky to obtain but basically they are the same. We can only rotate a piece if it's not obstructed by the wall (the left or right edges) or by another unit. Also a tetromino rotates around a central point which is considered to be one of its four units (or, better said, three out of four units rotate by ±90 degrees relative to the other unit which is fixed). To simplify the rotation method I've considered that all tetrominoes rotate around their third unit. Study the following image to better understand the concept.

In this picture you can see the four possible positions of each tetromino rotated by 90 degrees clockwise. The only tetromino that doesn't rotate is the O tetromino.
Step 27: The MotionManager Class
package com.tutsplus.active.tetris.dynamics { import com.tutsplus.active.tetris.data.Grid; public class MotionManager { private var _grid:Grid; private static const PI:Number = Math.PI // 3.14159265; public function MotionManager(grid:Grid) { _grid = grid; } } }
The constructor of this class takes the game grid as a parameter which is also referenced by the _grid
private variable. The private static constant PI
represents the value of Math.PI
and will be used in later calculations.
Step 28: Moving Down
To begin with let's take a look at the method used to move the tetromino down.
public function moveDown(tetromino:Tetromino):Boolean { var u1:Unit = tetromino.unit1; var u2:Unit = tetromino.unit2; var u3:Unit = tetromino.unit3; var u4:Unit = tetromino.unit4; if (u1.y < _grid.lines - 1 && u2.y < _grid.lines - 1 && u3.y < _grid.lines - 1 && u4.y < _grid.lines - 1) { if (!_grid.isUnitAt(u1.x, u1.y + 1) && !_grid.isUnitAt(u2.x, u2.y + 1) && !_grid.isUnitAt(u3.x, u3.y + 1) && !_grid.isUnitAt(u4.x, u4.y + 1)) { u1.y += 1; u2.y += 1; u3.y += 1; u4.y += 1; return true; } else return false; } else return false; }
Important! If you copy and paste the code you must make the necessary imports in the class.
Tip: If you're using FlashDevelop you can click on the class name you want to import anywhere in the code and pres Ctrl+Shift+1 to automatically insert the import statement.
First of all in this method we check whether each unit is not on the last line then we test if there are any units at the new locations. The new locations are calculated as the current locations in which we add 1 to the y
coordinate. If the locations in the grid are not occupied by any other landed unit and it's between bounds we move each unit by 1 on y
else the method returns false and the tetromino has landed.
Testing the MotionManager
Now that we've added our first method to the MotionManager
class let's test it along with the Grid
class.
In the Tetris
class add two new private variables _m
of type MotionManager
and _grid
of type Grid
.
private var _grid:Grid; private var _m:MotionManager;
Next add the highlighted lines below to the constructor. These define a new 20x10 grid and the motion manager which is making use of that grid.
public function Tetris() { _t = new Tetromino(); addChild(_t.unit1.toDisplay()); addChild(_t.unit2.toDisplay()); addChild(_t.unit3.toDisplay()); addChild(_t.unit4.toDisplay()); _grid = new Grid(10, 20); _m = new MotionManager(_grid); _g = new Gravity(); _g.register(moveDown); _g.start(); }
And to make use of the MotionManager
change the moveDown()
method like so:
private function moveDown(e:TimerEvent = null):void { _m.moveDown(_t); }
Test the movie and if everything is ok you should see the same result as the previous one, the only difference being that the MotionManager
class controls the downward movement and the tetromino will stop when it hits the bottom of the stage (if you've used the metatag that I've talked about in Step 23) meaning that the tetromino has hit the bottom of the grid.
Milestone 6
Next we'll take a look at moving the piece left and right.
Step 29: Moving Left and Right
Now let's create a method to move the tetromino to the left.
public function moveLeft(tetromino:Tetromino):Boolean { var u1:Unit = tetromino.unit1; var u2:Unit = tetromino.unit2; var u3:Unit = tetromino.unit3; var u4:Unit = tetromino.unit4; if (u1.x > 0 && u2.x > 0 && u3.x > 0 && u4.x > 0) { if (!_grid.isUnitAt(u1.x-1, u1.y) && !_grid.isUnitAt(u2.x-1, u2.y) && !_grid.isUnitAt(u3.x-1, u3.y) && !_grid.isUnitAt(u4.x-1, u4.y)) { u1.x -= 1; u2.x -= 1; u3.x -= 1; u4.x -= 1; return true; }else return false; } else return false; }
This method is similar to the downward movement method only that now we move the tetromino on the x
axis thus we're using the x
coordinate from which we subtract 1. Also we test that each unit isn't out of bounds meaning that they don't pass the left wall.
Moving the tetromino to the right is identically like moving it to the left the only difference being that we add 1 to the x
coordinate and check that the units aren't passing their right bound (the right wall).
public function moveRight(tetromino:Tetromino):Boolean { var u1:Unit = tetromino.unit1; var u2:Unit = tetromino.unit2; var u3:Unit = tetromino.unit3; var u4:Unit = tetromino.unit4; if (u1.x < _grid.columns - 1 && u2.x < _grid.columns -1 && u3.x < _grid.columns - 1 && u4.x < _grid.columns - 1) { if (!_grid.isUnitAt(u1.x + 1, u1.y) && !_grid.isUnitAt(u2.x + 1, u2.y) && !_grid.isUnitAt(u3.x + 1, u3.y) && !_grid.isUnitAt(u4.x + 1, u4.y)) { u1.x += 1; u2.x += 1; u3.x += 1; u4.x += 1; return true; }else return false; }else return false; }
You can add some keyboard events in the Tetris
class to test the left and right movement but I'll suggest that you wait until after we've wrapped up the MotionManager
class by adding rotation capability.
Step 30: Distance and Orientation
Before we start doing the actual rotation we'll define two methods, getDistance(x1, y1, x2, y2)
and getOrientation(x1, y1, x2, y2)
, which we'll use to calculate the distance and orientation between two points using their coordinates.
private function getDistance(x1:int, y1:int, x2:int, y2:int):Number { var dx:int = x2 - x1; var dy:int = y2 - y1; return Math.sqrt(dx * dx + dy * dy); } private function getOrientation(x1:int, y1:int, x2:int, y2:int):Number { var dx:int = x2 - x1; var dy:int = y2 - y1; var add:Number = 0; if ((dx < 0 && dy < 0) || (dx<0 && dy>0)) add = 180; else if (dx > 0 && dy < 0 ) add = 360; return Math.atan2(dy, dx); }
Step 31: Rotation Method
Now that we have defined the necessary methods to provide the distance and orientation between two points we can start creating the rotation method. This method will apply for all tetrominoes except the O tetromino.
public function rotate(tetromino:Tetromino, angle:int = 90):Boolean { if (tetromino.type != TetrominoType.O) { var x:int = tetromino.unit3.x; var y:int = tetromino.unit3.y; var x1:int = tetromino.unit1.x; var x2:int = tetromino.unit2.x; var x3:int = tetromino.unit4.x; var y1:int = tetromino.unit1.y; var y2:int = tetromino.unit2.y; var y3:int = tetromino.unit4.y; var d1:Number = getDistance(x1, y1, x, y); var d2:Number = getDistance(x2, y2, x, y); var d3:Number = getDistance(x3, y3, x, y); var o1:Number = getOrientation(x1, y1, x, y); var o2:Number = getOrientation(x2, y2, x, y); var o3:Number = getOrientation(x3, y3, x, y); o1 -= angle * PI / 180; o2 -= angle * PI / 180; o3 -= angle * PI / 180; x1 = x + Math.round(d1 * Math.cos(o1)); y1 = y + Math.round(d1 * Math.sin(o1)); x2 = x + Math.round(d2 * Math.cos(o2)); y2 = y + Math.round(d2 * Math.sin(o2)); x3 = x + Math.round(d3 * Math.cos(o3)); y3 = y + Math.round(d3 * Math.sin(o3)); if (!_grid.isUnitAt(x1, y1) && !_grid.isUnitAt(x2, y2) && !_grid.isUnitAt(x3, y3)) { tetromino.unit1.x = x1; tetromino.unit1.y = y1; tetromino.unit2.x = x2; tetromino.unit2.y = y2; tetromino.unit4.x = x3; tetromino.unit4.y = y3; return true; }else return false; }else return false; }
Okay. I know this looks hard and geeky but it is basic trigonometry so let me explain.
First of all we calculate the distance and orientation between the fixed unit (unit 3) and the other units (units 1, 2 and 4).
var d1:Number = getDistance(x1, y1, x, y); var d2:Number = getDistance(x2, y2, x, y); var d3:Number = getDistance(x3, y3, x, y); var o1:Number = getOrientation(x1, y1, x, y); var o2:Number = getOrientation(x2, y2, x, y); var o3:Number = getOrientation(x3, y3, x, y);
Note: The orientation represents the angle between the positive direction of the X axis and the direction between two points measured counter clockwise.
Then we add/subtract 90 degrees to/from the current orientation of each two units, obtaining the new orientation.
o1 -= angle * PI / 180; o2 -= angle * PI / 180; o3 -= angle * PI / 180;
After which we calculate the new coordinates of each unit (unit 1, 2 and 4) using the distance and orientation and round them up so that we obtain integer values.
x1 = x + Math.round(d1 * Math.cos(o1)); y1 = y + Math.round(d1 * Math.sin(o1)); x2 = x + Math.round(d2 * Math.cos(o2)); y2 = y + Math.round(d2 * Math.sin(o2)); x3 = x + Math.round(d3 * Math.cos(o3)); y3 = y + Math.round(d3 * Math.sin(o3));
Note: The coordinates of a point are calculated using the distance and orientation using the following formula:

in which D represents the distance and θ (pronounced "theta") represents the orientation.
And finally we test if the new positions are empty in the grid and move the units accordingly. If any of the three positions are occupied the method returns false and rotation doesn't happen.
Step 32: Testing Sideways Movement and Rotation
Now that we've finished our class adding all necessary capabilities (moving down, sideways and rotating) let's give it a test run and see how our game is growing more and more.
In the Tetris
class add the following code on the last line of the constructor:
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
Then create the onKeyDown()
event handler in which we use a switch-case statement to test what key was pressed and do the corresponding action.
private function onKeyDown(e:KeyboardEvent):void { switch(e.keyCode) { case Keyboard.LEFT: _m.moveLeft(_t); break; case Keyboard.RIGHT: _m.moveRight(_t); break; case Keyboard.UP: _m.rotate(_t); default:break; } }
Run the project and see the result. Now you should be able to move the tetromino left and right and rotate it. As you can see it will remain between the bounds of the defined grid meaning that a unit can only move 10 spaces maximum to the left or right and it will not cross over the bottom line.
Milestone 7
Step 33: Encapsulating the Playfield
Now that our game is growing we need to separate the playfield from the rest of the game. By this mean the grid in which the pieces are added. The GameCanvas
class will represent the playfield separating it from the other game elements which we'll add later (scoreboard, piece preview, buttons).
package com.tutsplus.active.tetris.display { import com.tutsplus.active.tetris.data.Grid; import flash.display.Sprite; import flash.filters.GlowFilter; public class GameCanvas extends Sprite { private var _grid:Grid; private var _mask:Sprite; public function GameCanvas(columns:uint = 10, lines:uint = 20) { _grid = new Grid(columns, lines); drawCanvas(); } private function drawCanvas():void { graphics.clear(); graphics.lineStyle(1, 0x006699, 0.8, true, "none"); graphics.beginFill(0xdedede, 0.0); for (var i:uint = 0; i < _grid.lines; i++) { for (var j:int = 0; j < _grid.columns; j++) { graphics.drawRect(j * Box.SIZE, i * Box.SIZE, Box.SIZE, Box.SIZE); } } graphics.endFill(); filters = [new GlowFilter(0x006699, 0.8, 5, 5, 2, 10)]; } private function addMask():void { _mask = new Sprite(); _mask.graphics.clear(); _mask.graphics.beginFill(0); _mask.graphics.drawRect(0, 0, _grid.columns * Box.SIZE + 1, _grid.lines * Box.SIZE + 1); _mask.graphics.endFill(); addChild(_mask); mask = _mask; } } }
The first thing we need to do in the GameCanvas
class is to associate a grid to it.
With the drawCanvas()
we'll draw an actual grid on the playfield using the drawing API and set a glow filter on it.
The addMask()
method creates the mask for the playfield which is used to mask two grid lines in which the newly added piece resides.
Step 34: Grouping Features
Now that we've created the canvas the next step is to add all the features previously created like gravity and motion. We'll start off by creating the variable which will hold the corresponding classes.
private var _gravity:Gravity; private var _moManager:MotionManager;
After this we'll create a new method called init()
in which we'll instantiate all the necessary elements (for now only the _gravity
and _moManager()
objects and the addMask()
method).
private function init():void { _gravity = new Gravity(); _moManager = new MotionManager(_grid); addMask(); }
Notice that we've passed the previously created grid as an argument when creating the MotionManager
object.
Also we'll need to call the init()
method from within the constructor.
public function GameCanvas(columns:uint = 10, lines:uint = 20) { _grid = new Grid(columns, lines); drawCanvas(); init(); }
The last thing we need to add for now is a tetromino and a tetromino holder so let's do that right now. Add three new variables called _currentPiece
, _nextPiece
and _pieceHolder
.
private var _currentPiece:Tetromino; private var _pieceHolder:Sprite;
Next add the highlighted lines in the init()
method:
private function init():void { _gravity = new Gravity(); _moManager = new MotionManager(_grid); _pieceHolder = new Sprite(); addMask(); addChild(_pieceHolder); }
Step 35: Testing the Canvas
Before going any further let's test our canvas and see if everything goes as expected. Clear the Tetris
class and add only one private variable in it of type GameCanvas
named _gameCanvas
. After which create the addCanvas()
method in which we instantiate the canvas object and add it to the display list.
package com.tutsplus.active.tetris { import com.tutsplus.active.tetris.display.GameCanvas; import flash.display.Sprite; [SWF(width = "550", height = "600", frameRate = "31", backgroundColor = "0x000912")] public class Tetris extends Sprite { private var _gameCanvas:GameCanvas; public function Tetris() { addCanvas(); } private function addCanvas():void { _gameCanvas = new GameCanvas(); addChild(_gameCanvas); } } }
If all your code is similar to mine then you should see a bluish 20x10 grid on the screen. This is the canvas or playfield where the pieces will appear and move.
Milestone 8
Next let's add the rest of the functionality.
Step 36: Adding the Current Piece
What we should do next is create the current tetromino and add it to the canvas.
First of all write the createPieces()
method which will be used to generate a new random tetromino.
private function createPieces():void { _currentPiece = new Tetromino(); }
Next create the addNew()
which adds the visual representations of the tetrominos units in the display list, precisely in the _pieceHolder
sprite created in Step 34.
private function addNew():void { _pieceHolder.addChild(_currentPiece.unit1.toDisplay()); _pieceHolder.addChild(_currentPiece.unit2.toDisplay()); _pieceHolder.addChild(_currentPiece.unit3.toDisplay()); _pieceHolder.addChild(_currentPiece.unit4.toDisplay()); }
We will want to remove the units from the _pieceDisplay
sprite eventually when a new piece is generated so we'll add another method which does exactly the opposite.
private function removeOld():void { while (_pieceHolder.numChildren > 0) _pieceHolder.removeChildAt(_pieceHolder.numChildren-1); }
Last thing we'll want to do is wrap the whole process into one method which we'll call updateView()
. This method will be used to render the pieces at each movement.
private function updateView():void { removeOld(); addNew(); }
Step 37: Offsetting the Units
If you add the createPieces()
and updateView()
method calls after the last line inside init()
you should see a random tetromino appearing on the stage. The only problem is the position of this piece being next to the left wall on the first two lines.

We want these pieces to appear two lines above the first line (being masked) and in the middle of the grid.
The offsetPiece()
method will have the purpose of moving the piece two lines above the first line and in the middle of the grid. The I tetromino will start from the fourth column (x = 3) and only at a line above the top one (y = -1). Any other piece will start from the fifth column (x= 4) and from two lines above the first one (y = -2).
private function offsetPiece():void { if (_currentPiece.type == TetrominoType.I) { _currentPiece.unit1.x += 3; _currentPiece.unit2.x += 3; _currentPiece.unit3.x += 3; _currentPiece.unit4.x += 3; _currentPiece.unit1.y += -1; _currentPiece.unit2.y += -1; _currentPiece.unit3.y += -1; _currentPiece.unit4.y += -1; }else { _currentPiece.unit1.x += 4; _currentPiece.unit2.x += 4; _currentPiece.unit3.x += 4; _currentPiece.unit4.x += 4; _currentPiece.unit1.y += -2; _currentPiece.unit2.y += -2; _currentPiece.unit3.y += -2; _currentPiece.unit4.y += -2; } }
To achieve the desired effect we simply add/subtract the required values from the coordinates of each unit in the current piece after using an if-else statement to check the tetromino type.
The last thing we need to do is change the createPieces()
method accordingly.
private function createPieces():void { _currentPiece = new Tetromino(); offsetPiece(); }
If you test the movie now you should only see the grid as in the previous milestone. Remove these two lines from the init()
method and let's go forth.
createPieces(); updateView();
Step 38: Start, Stop, Pause, Resume
Before diving deeper into the code let's add two more variables to our GameCanvas
class.
private var _clearedLines:uint, _level:uint;
-
_clearedLines
will hold the total number of cleared lines in a level -
_level
will hold the current level of the game
Starting the game
First of all let's take a look at the start()
method.
public function start(level:uint = 0):void { createPieces(); updateView(); _gravity.velocity = _level = level; _gravity.register(movePiece); _gravity.start(); _clearedLines = 0; _grid.clearLines(); } private function movePiece(e:TimerEvent = null):void { _moManager.moveDown(_currentPiece); }
As you can see I've added the movePiece()
method for moving the tetromino down and registered it to the gravity. There's a e:TimerEvent = null
parameter because the gravity class requires it and it's set to null
as we may need to call it without a TimerEvent
being fired.
Stopping the game
The stop()
method is really easy to understand. In it we simply stop the gravity, unregister the associated action and set the current piece to null
.
public function stop():void { _gravity.stop(); _gravity.unregister(movePiece); _currentPiece = null; }
Pausing and resuming the game
public function pause():void { _gravity.stop(); unregisterKeys(); } public function resume():void { _gravity.start(); registerKeys(); }
By pausing and resuming the game we only need to stop and respectively start the gravity. The highlighted lines contain two methods that we'll discuss next.
Step 39: Keyboard Functionality
The last thing we need to do before adding extra features to the game is restore keyboard functionality, which is basically the same as in Step 32 with only minor differences.
Start off by creating two methods used to register and unregister the keyboard events. These are just adding and removing the KeyboardEvent.KEY_DOWN
events on the stage and give us a shortcut so that we don't have to write too long lines of code.
private function registerKeys():void { if (stage) stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyPress); } private function unregisterKeys():void { if (stage) stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyPress); }
Now in the onKeyPress(e:KeyboardEvent)
event handler we use a switch-case statement in order to check what key was pressed and execute the corresponding function.
private function onKeyPress(e:KeyboardEvent):void { switch(e.keyCode) { case Keyboard.LEFT: moveLeft(); break; case Keyboard.RIGHT: moveRight(); break; case Keyboard.UP: rotate("right"); break; case Keyboard.DOWN: quickDrop(); break; default: break; } if (e.charCode == "x".charCodeAt()) rotate("right"); if (e.charCode == "z".charCodeAt()) rotate("left"); stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); unregisterKeys(); updateView(); }
We unregister the keys so that the event isn't fired continuously and we also add an event listener for KEY_UP
events.
Before going to the next step add the following blank method. We will discuss each one of them separately.
private function moveLeft():void { } private function moveRight():void { } private function quickDrop():void { } private function rotate(side:String):void { } private function onKeyUp(e:KeyboardEvent):void { }
Step 40: Moving Left, Right and Rotating
In the moveLeft()
and moveRight()
methods we just use the motion manager to move the current tetromino left or right.
private function moveLeft():void { _moManager.moveLeft(_currentPiece); } private function moveRight():void { _moManager.moveRight(_currentPiece); }
The rotate()
method is just as simple the only difference being that we check whether the argument says "left" or "right" and rotate the piece appropriately.
private function rotate(side:String):void { if (side == 'left') _moManager.rotate(_currentPiece, -90); else if (side == 'right') _moManager.rotate(_currentPiece); }
Step 41: Quick Drop
Dropping refers to moving a tetromino down at a higher speed when holding a button down.
In Tetris there are two types of drops: soft drop and hard drop as explained next.
- Soft drop – a piece is moved downwards at a higher speed when holding a specific button down
- Hard drop – a piece is instantly moved down on the floor when a specific button is pressed
We are going to use the first kind of drop in our game and we're going to refer to it as "quick drop". For this some minor modifications must be made to the Gravity
class.
First we're going to add a constant in it that represents the quick drop rate (the smaller the number the faster the drop). The number represents the number of milliseconds that a tetromino takes to go down by one unit.
private const QUICK_DELAY:uint = 25; //ms
Next we'll have to add a setter to the class, which we can use to change the timer delay from normal to quick and vice versa.
public function set quickDropMode(value:Boolean):void { if (value) _timer.delay = QUICK_DELAY; else _timer.delay = 850 - 100 * _velocity; }
Also we'll add a getter which can be used to see if the gravity is in quick drop mode or not.
public function get quickDropMode():Boolean { return (_timer.delay == QUICK_DELAY); }
Now that the gravity class is complete we need to add the rest of the logic back in the GameCanvas
class. Add the highlighted lines to the quickDrop()
method to finish the implementation.
private function quickDrop():void { unregisterKeys(); stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); _gravity.quickDropMode = true; }
The unregisterKeys()
method is used because we want to restrict any other movement while in quick drop.
Step 42: Completing and Testing Movement
The last method that we want to complete in our GameCanvas
class is the event handler for KEY_UP
events. When the user releases a key we need to subscribe for KEY_DOWN
events once again and if in quick drop mode to return into normal gravity mode.
private function onKeyUp(e:KeyboardEvent):void { if (e.keyCode == Keyboard.DOWN) _gravity.quickDropMode = false; stage.removeEventListener(KeyboardEvent.KEY_UP, onKeyUp); registerKeys(); }
Lastly we need to register the keys right when the game starts (back into the start()
method).
public function start(level:uint = 0):void { createPieces(); updateView(); _gravity.velocity = _level = level; _gravity.register(movePiece); _gravity.start(); _clearedLines = 0; _grid.clearLines(); registerKeys(); }
If you test the movie right now nothing would happen and you'd only see the grid on the stage. That's because the game isn't started. To start the game go back into the Tetris
class and add the highlighted line in the addCanvas()
method.
private function addCanvas():void { _gameCanvas = new GameCanvas(); addChild(_gameCanvas); _gameCanvas.start(); }
Milestone 9
Step 43: Landing a Piece
Until now we can do most of the actions required in a basic Tetris game but we only have a tetromino spawned and when it lands nothing happens. Next we'll need to add some action for when a tetromino touches the ground (or another tetromino) in which we want to spawn the next piece for now. Later we'll further extend the game by checking for full lines or end the game when a piece lands.
Before creating the actual method to land a piece we need to add another variable which will be the sprite that holds the landed pieces.
private var _mainHolder:Sprite;
Also instantiate the _mainHolder
in the init()
method and add it to the display list.
private function init():void { _gravity = new Gravity(); _moManager = new MotionManager(_grid); _pieceHolder = new Sprite(); _mainHolder = new Sprite(); addMask(); addChild(_pieceHolder); addChild(_mainHolder); }
In the landPiece
we simply move all the units from the _pieceHolder
into the _mainHolder
private function landPiece():void { removeOld(); _mainHolder.addChild(_currentPiece.unit1.toDisplay()); _mainHolder.addChild(_currentPiece.unit2.toDisplay()); _mainHolder.addChild(_currentPiece.unit3.toDisplay()); _mainHolder.addChild(_currentPiece.unit4.toDisplay()); }
However this is not enough, after we land the piece we need to create a new one. To achieve this we need to modify the movePiece()
a bit.
private function movePiece(e:TimerEvent = null):void { if (!_moManager.moveDown(_currentPiece)) { landPiece(); createPieces(); updateView(); } }
In here we test if the piece can move down and if not we first land it and then we create a new one.
Step 44: Setting Units in the Grid
If you test the movie now a new piece will be spawned as the old one hits the ground. however there's a glitch: the pieces blend together. To fix this we need to set the units of the landed piece into the grid.
private function setUnits():void { if (_currentPiece.unit1.y > 0 && _currentPiece.unit2.y > 0 && _currentPiece.unit3.y > 0 && _currentPiece.unit4.y > 0) { _grid.setUnit(_currentPiece.unit1); _grid.setUnit(_currentPiece.unit2); _grid.setUnit(_currentPiece.unit3); _grid.setUnit(_currentPiece.unit4); } else stop(); }
The setUnits()
method checks whether all the units have positive y
coordinates so that we won't get an error if the tetromino is outside the grids bounds. Then we simply set in the grid each of the four units from the current piece.
And lastly we need to execute the setUnits()
method from within landPiece()
.
private function landPiece():void { removeOld(); _mainHolder.addChild(_currentPiece.unit1.toDisplay()); _mainHolder.addChild(_currentPiece.unit2.toDisplay()); _mainHolder.addChild(_currentPiece.unit3.toDisplay()); _mainHolder.addChild(_currentPiece.unit4.toDisplay()); setUnits(); }
Now you can test the movie and see how pieces are spawned and landed.
Milestone 10
Note: If you keep the down button pressed even after the current piece has landed the newly spawned piece will also be quick dropping. If you don't want this behavior simply set the quickDropMode
property of the _gravity
object to false inside the if-statement from the movePiece()
method.
private function movePiece(e:TimerEvent = null):void { if (!_moManager.moveDown(_currentPiece)) { _gravity.quickDropMode = false; landPiece(); createPieces(); updateView(); } }
Step 45: Removing Filled Lines
Next step in creating our Tetris game is adding methods which remove full lines and compress the remaining empty lines.
We're going to start by adding a method to the Grid
class which removes the lines from the actual grid so open up the class and add the clearFullLines()
method.
public function clearFullLines():uint { var lines:uint = 0; var isFull:Boolean = true; for (var i:int = 0; i < _lines; i++) { for (var j:int = 0; j < _columns; j++) { if (_grid[i][j] == null) { isFull = false; break; } } if (isFull == true) { _grid.splice(i, 1); _grid.unshift(new Array(_columns)); lines++; } isFull = true; } return lines; }
This method uses two for loops to check whether any line is filled with units and removes the lines that are after which it adds a new empty line at top. Also the method returns the number of removed lines.
Step 46: Removing Lines From Canvas
Now that we defined a method to remove filled lines from the grid let's use it in the canvas and remove the lines from there also.
Back in the GameCanvas
class add a new method called removeFullLines()
:
private function removeFullLines():void { var fullLines:uint = _grid.clearFullLines(); if (fullLines > 0) { _clearedLines += fullLines; updateMain(); } }
This method calls upon another method updateMain()
. The updateMain()
method removes all units from the canvas including those that are no longer part of the grid using removeAllFromMain()
after which adds the remaining units from the grid back to the display list.
private function updateMain():void { // Removes all unit from the main holder including those removed from the grid removeAllFromMain(); // Adds the units from the grid to the display list for (var i:int = 0; i < _grid.lines; i++) for (var j:int = 0; j < _grid.columns; j++) if (_grid.isUnitAt(j, i)) _mainHolder.addChild(_grid.getUnitAt(j, i).toDisplay()); } private function removeAllFromMain():void { while (_mainHolder.numChildren > 0) _mainHolder.removeChildAt(_mainHolder.numChildren - 1); }
If we don't update the units from the main holder then the filled lines would be removed from the grid but not from the canvas and the well would be filled with units that are no longer part of the grid.
Only thing we have to do now is to call the removeFullLines()
method each time a piece lands (meaning in the landPiece()
method).
private function landPiece():void { removeOld(); _mainHolder.addChild(_currentPiece.unit1.toDisplay()); _mainHolder.addChild(_currentPiece.unit2.toDisplay()); _mainHolder.addChild(_currentPiece.unit3.toDisplay()); _mainHolder.addChild(_currentPiece.unit4.toDisplay()); setUnits(); removeFullLines(); }
also we'll call this method whenever a new game is started.
public function start(level:uint = 0):void { removeAllFromMain(); createPieces(); updateView(); // rest of the code }
Step 47: Fixing It!
If you test the movie now you should see that whenever a piece land and lines are filled these lines are removed. However there's a problem; the remaining lines don't shift down. Actually they shift the _grid
object but the remaining units need to have their coordinates updated also.

To fix the positioning problem go back into the Grid
class and add the following method:
private function updateUnits():void { for (var i:int = 0; i < _lines; i++) for (var j:int = 0; j < _columns; j++) if (_grid[i][j] != null) { Unit(_grid[i][j]).x = j; Unit(_grid[i][j]).y = i; } }
By doing this we go through each position in the grid and if a unit is set at the corresponding coordinates we update that unit's position.
And to make this work we need to add a call to this method right after the return
statement in the clearFullLines()
method.
/// updateUnits() return lines;
Press Ctrl+Enter or F5 on the keyboard (if using FlashDevelop) to run the project and see how every filled line is disappearing.
Milestone 11
Step 48: Scoring
We are done with all the game logic for the moment. However, our game currently misses an essential piece in any game: score.
Next we'll focus on creating a score display widget which will display the score and the current level as well.
Create a new class ScoreDisplay
which extends the Sprite
class.

package com.tutsplus.active.tetris.display { import flash.display.Sprite; import flash.text.TextField; public class ScoreDisplay extends Sprite { private var _scoreField:TextField; private var _levelField:TextField; private var _score:uint = 0, _level:uint = 1; public function ScoreDisplay() { _scoreField = new TextField(); _levelField = new TextField(); draw(); } private function draw():void { graphics.clear(); graphics.drawRect(0, 0, 7 * Box.SIZE, 3 * Box.SIZE); } } }
In this class we have the following variables:
-
_scoreField
and_levelField
are the text fields for displaying the score and respectively the level -
_score
will hold the actual score points. We set it to 0 because initially the game score is zero -
_level
will hold the current level which is an unsigned integer and is set initially to 1
... and a method draw()
which draws an invisible rectangle surrounding the score widghet.
Step 49: Configuring the Text Fields
Because we have two text fields which will look the same let's create a method which sets up a text field to look like a digital board. This is what addField(textField)
method does.
private function addField(textField:TextField):void { addChild(textField); var format:TextFormat = new TextFormat(); format.font = 'Digital'; format.size = 30; format.bold = true; format.italic = true; format.color = 0x0062bd; format.align = TextFormatAlign.JUSTIFY; textField.autoSize = TextFieldAutoSize.LEFT; textField.defaultTextFormat = format; textField.selectable = false; textField.antiAliasType = AntiAliasType.ADVANCED; textField.embedFonts = true; textField.sharpness = 50; }
And to set up the two fields add the highlighted lines to the class constructor:
public function ScoreDisplay() { _scoreField = new TextField(); _levelField = new TextField(); addField(_scoreField); addField(_levelField); draw(); }
Note: On the third line of the addField()
method we assign a font called 'Digital'. This font is actually named DS-Digital and we'll be embedding it later in our game.
Step 50: Adding the Text to the Fields
Right now both the text fields are empty so let's add some text to them. For this we'll use two methods: one for the score field and one for the level field.
private function setScoreText():void { _scoreField.text = "Score: "; if (_score < 10) _scoreField.appendText("00000" + _score.toString()); else if (_score < 100) _scoreField.appendText("0000" + _score.toString()); else if (_score < 1000) _scoreField.appendText("000" + _score.toString()); else if (_score < 10000) _scoreField.appendText("00" + _score.toString()); else if (_score < 100000) _scoreField.appendText("0" + _score.toString()); else _scoreField.appendText(_score.toString()); }
In the setScoreText()
method we use multiple if-else statements to add leading zeros to the score text depending on how large the score is.
private function setLevelText():void { _levelField.text = "Level:\t "; if (_level < 10) _levelField.appendText(" " + _level.toString()); else _levelField.appendText(_level.toString()); }
For the level field we only have to append the level number to the "Level:" text.
And to update the text in the fields from the beginning add the highlighted lines in the constructor:
addField(_scoreField); addField(_levelField); setScoreText(); setLevelText(); _levelField.y = _scoreField.y + _scoreField.height;
In the third highlighted line we position the level field below the score field.
Step 51: Resetting
Whenever the game is ended/stopped we need to reset the score and the level in the widget. For this we'll use the reset()
method shown below:
public function reset():void { _score = 0; _level = 1; setScoreText(); setLevelText(); }
Simple as that!
Step 52: Adding the Widget to the Game
Okay. We've created the widget so let's add it to our Tetris
class.
First open the Tetris
class and add a private variable to reference the score widget.
private var _scoreDisplay:ScoreDisplay;
Then write the following method to instantiate and add the ScoreDisplay
object to the display list.
private function addScoreDisplay():void { _scoreDisplay = new ScoreDisplay(); _scoreDisplay.x = _gameCanvas.x + _gameCanvas.width + 30; addChild(_scoreDisplay); }
Also add a call to this method within the constructor just after the addCanvas()
line.
addCanvas(); addScoreDisplay();
If you test the movie now you won't be able to see anything because the font used for the fields does not exist.
Step 53: Embedding Fonts and Testing
First go to this webpage http://www.dafont.com/ds-digital.font and download the DS-Digital font (or whatever font you want for that matter). We we'll only use the normal and bold-italic font faces -- meaning the DIGI.TTF and DIGIT.TTF files -- so place them inside your project folder. I have copied mine inside the lib folder created by default in FlashDevelop.

Then add the next two lines in the Tetris
class just before the variables declaration block to embed the fonts in your game:
[Embed(source = '../../../../../lib/DS-DIGI.TTF', fontName = 'Digital'/*, embedAsCFF = 'false'*/)] private var digiNormal:Class; [Embed(source='../../../../../lib/DS-DIGIT.TTF', fontName = 'Digital', fontWeight = 'bold', fontStyle = 'italic'/*, embedAsCFF = 'false'*/)] private var digiBoldItalic:Class;
Note: The source
attribute defines the file path to the font files. You will have to replace it with your own file path.
The pieces of code I've commented out (embedAsCFF
) are used when embedding fonts in Flash CS5/Adobe Flash Builder (using Flash Player 10.1) using the new Text Layout Framework (TLF) engine meaning that the font will be embedded as Compact File Format.
By testing the game you should see the scoreboard appear but the score won't update.
Milestone 12
Step 54: Game Events
Right now it's time to write our first event for the game. We'll use this event for various actions: updating the score, updating the level and updating the piece preview.
Start by adding a new event in the project:

The highlighted lines represent what I've added to the basic custom event class.
package com.tutsplus.active.tetris.events { import flash.events.Event; public class TetrisGameEvent extends Event { private var _level:uint = 0; private var _lines:uint = 0; private var _pieceType:String = ''; private var _params:Object; public static const LEVEL_INCREASE:String = 'levelIncrease'; public static const SCORE_UPDATE:String = 'scoreUpdate'; public static const PIECE_UPDATE:String = 'pieceUpdate'; public function TetrisGameEvent(type:String, params:Object = null, bubbles:Boolean = false, cancelable:Boolean = false) { _params = params; super(type, bubbles, cancelable); if (_params) { _level = params.hasOwnProperty('lvl') ? params.lvl : 0; _lines = params.hasOwnProperty('lines') ? params.lines : 0; _pieceType = params.hasOwnProperty('pieceType') ? params.pieceType : ''; } } public function get level():uint { return _level; } public function get lines():uint { return _lines; } public function get pieceType():String { return _pieceType; } public override function clone():Event { return new TetrisGameEvent(type, _params, bubbles, cancelable); } public override function toString():String { return formatToString("TetrisGameEvent", "type", "bubbles", "cancelable", "eventPhase"); } } }
The private variables represent the level passed to the event, the number of cleared lines when the event is fired, and the piece type when a new piece is spawned. The _params
object will hold all the parameters passed through the constructor. We pass the parameters within an object so that we won't be forced to pass all of the every time.
The static constants represent the three possible event types:
-
LEVEL_INCREASE
event will be fired every time when the level is increased -
SCORE_UPDATE
event will be fired whenever the score is updated (lines are cleared) -
PIECE_UPDATE
event will be triggered each time when a new piece is spawned
Step 55: Scoring System
Before going further let's talk a bit about the game's scoring system.
In accordance with the Tetris Wiki we will use an implementation of the original Nintendo scoring system in which line clearings are scored by combos. The following table defines the scoring method:

I think this is simple enough and quite fair for this type of game. You can implement whatever scoring system you want by using a timer, the number of spaces you've quick dropped a piece, and so on and so forth.
Step 56: Scoreboard
Now that we have our event ready to be fired and we have a basic understanding of how score points should be earned we'll modify the score display widget to change the score and level based on the parameters passed through this event. Open the ScoreDisplay
class and add the following two methods:
public function updateScore(e:TetrisGameEvent):void { var cs:uint; switch(e.lines) { case 1: cs = 40 * (_level); break; case 2: cs = 100 * (_level); break; case 3: cs = 300 * (_level); break; case 4: cs = 1200 * (_level); break; } _score += cs; setScoreText(); }
As you can see this method uses a switch-case statement to calculate the score based on the number of lines and level.
public function updateLevel(e:TetrisGameEvent):void { _level = e.level + 1; setLevelText(); }
Updating the level is just as simple as incrementing the current level by one.
Step 57: Dispatching...
In order for everything to work first we'll add a new method in the GameCanvas
class...
private function levelUp():void { _clearedLines = 0; _level ++; dispatchEvent(new TetrisGameEvent(TetrisGameEvent.LEVEL_INCREASE, {lvl:_level})); _gravity.velocity = _level; }
... and call it whenever 10 lines have been cleared more precisely in the removeFullLines()
method:
public function removeFullLines():void { var fullLines:uint = _grid.clearFullLines(); if (fullLines > 0) { _clearedLines += fullLines; updateMain(); dispatchEvent(new TetrisGameEvent(TetrisGameEvent.SCORE_UPDATE, { lines:fullLines } )); if (_clearedLines > 9) levelUp(); } }
Here we also dispatch a SCORE_UPDATE
event for notifying listeners that the score has changed.
The other places where we should add event dispatchers are:
- when creating a new piece. This will be used to update the piece preview later on.
private function createPieces():void { if (!_nextPiece) _nextPiece = new Tetromino(); _currentPiece = _nextPiece; _nextPiece = new Tetromino(); offsetPiece(); this.dispatchEvent(new TetrisGameEvent(TetrisGameEvent.PIECE_UPDATE, {pieceType:_nextPiece.type})); }
- and when starting the game
public function start(level:uint = 0):void { removeAllFromMain(); createPieces(); updateView(); _gravity.velocity = _level = level; _gravity.register(movePiece); _gravity.start(); _clearedLines = 0; _grid.clearLines(); registerKeys(); dispatchEvent(new TetrisGameEvent(TetrisGameEvent.LEVEL_INCREASE, {lvl:_level})); dispatchEvent(new TetrisGameEvent(TetrisGameEvent.SCORE_UPDATE)); }
Step 58: ... and Listening
Now we only have to add event listeners for the corresponding events back in the Tetris
class...
private function addCanvas():void { _gameCanvas = new GameCanvas(); _gameCanvas.addEventListener(TetrisGameEvent.SCORE_UPDATE, onScoreUpdate); _gameCanvas.addEventListener(TetrisGameEvent.LEVEL_INCREASE, onLevelIncrease); _gameCanvas.addEventListener(TetrisGameEvent.PIECE_UPDATE, updateView); addChild(_gameCanvas); }
... and their corresponding event handler functions:
private function onLevelIncrease(e:TetrisGameEvent):void { _scoreDisplay.updateLevel(e); } private function onScoreUpdate(e:TetrisGameEvent):void { if (e.lines > 0) _scoreDisplay.updateScore(e); else _scoreDisplay.reset(); } private function updateView(e:TetrisGameEvent):void { }
Note: I've moved the line _gameCanvas.start();
from the addCanvas()
method to the constructor of the class so that it won't throw an error upon compiling.
public function Tetris() { addCanvas(); addScoreDisplay(); _gameCanvas.start(); }
You can now test the game and see the result.
Milestone 13
Step 59: Previewing Next Piece
As the title of this step says now we're going to create yet another widget which will display the next piece in queue.
If you haven't noticed in Step 57 I've made the following modifications to the createPieces()
method:
if (!_nextPiece) _nextPiece = new Tetromino(); _currentPiece = _nextPiece; _nextPiece = new Tetromino();
By doing this two pieces are created at a time: the current one and the next one.
Our new class which instance will display the next piece to be spawned will be called PieceDisplay
:
package com.tutsplus.active.tetris.display { import flash.display.Sprite; import flash.text.AntiAliasType; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.text.TextFormat; public class PieceDisplay extends Sprite { private var _label:TextField; public function PieceDisplay() { _label = new TextField(); addChild(_label); draw(); setLabel(); } // Used to configure the label private function setLabel():void { var format:TextFormat = new TextFormat(); format.font = "Digital"; format.size = 30; format.bold = true; format.color = 0x0062bd; format.italic = true; _label.autoSize = TextFieldAutoSize.LEFT _label.embedFonts = true; _label.defaultTextFormat = format; _label.text = "Next piece:"; _label.selectable = false; _label.antiAliasType = AntiAliasType.ADVANCED; _label.sharpness = 50; } // Used to draw an invisible rectangle around the widtget private function draw():void { graphics.clear(); graphics.drawRect(0, 0, 7*Box.SIZE, 5 * Box.SIZE); } } }
In this class we have a _label
which is the text that says "Next piece" and two methods draw()
and setLabel()
.
Step 60: Setting the New Piece
To display a tetromino in the preview widget we'll need to add its corresponding units to the display list.
private function addNew(value:Tetromino):void { offsetPiece(value); addChild(value.unit1.toDisplay()); addChild(value.unit2.toDisplay()); addChild(value.unit3.toDisplay()); addChild(value.unit4.toDisplay()); }
The highlighted line is used to call a method to offset the tetromino by a certain amount so that it's displayed centered.
private function offsetPiece(piece:Tetromino):void { piece.unit1.x += 1 piece.unit1.y += 2; piece.unit2.x += 1 piece.unit2.y += 2; piece.unit3.x += 1 piece.unit3.y += 2; piece.unit4.x += 1 piece.unit4.y += 2; }
To remove a tetromino we'll have to remove all children of the tetromino that are units.
public function clear():void { while (numChildren > 1) { if(getChildAt(numChildren - 1) !== _label) removeChildAt(numChildren - 1) else continue; } }
Lastly we define a setter to set the new piece:
public function set piece(value:Tetromino):void { clear(); addNew(value); }
We made this method public because we may want to access it from outside the class.
Step 61: Updating the Tetris Class
Last thing we need to do is update the Tetris
class by adding the PieceDisplay
widget to it.
Open the class and add a new private variable private var _pieceDisplay:PieceDisplay;
then add the widget to the game through the addPieceDisplay()
method:
public function Tetris() { addCanvas(); addScoreDisplay(); addPieceDisplay(); _gameCanvas.start(); } // ... private function addPieceDisplay():void { _pieceDisplay = new PieceDisplay(); addChild(_pieceDisplay); _pieceDisplay.x = _scoreDisplay.x; _pieceDisplay.y = _scoreDisplay.y + _scoreDisplay.height; }
and finally update the widget in the event handler for PIECE_UPDATE
events.
private function updateView(e:TetrisGameEvent):void { if(TetrominoType.isValidType(e.pieceType)) _pieceDisplay.piece = new Tetromino(e.pieceType); else _pieceDisplay.clear(); }
Test the game and see how it runs.
Milestone 14
Step 62: DigitalButton Class
To complete our game we need to add one last thing which is fundamental to any game: Start, Pause and Resume buttons.
For the purpose of this tutorial I've made a custom button class which creates a button that resembles a digital button from a old console game. I will not explain how I've created it because it's outside the scope of this tutorial and it's only basic programming. Here's the button class:
package com.tutsplus.active.tetris.display { import flash.display.Sprite; import flash.events.MouseEvent; import flash.filters.GlowFilter; import flash.text.AntiAliasType; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.text.TextFormat; /** * A button similar to the digital buttons from old console games. * ... * @author Alexxcz */ public class DigitalButton extends Sprite { private var _field:TextField; private var _enabled:Boolean; /** * Create a new DigitalButton object with the specified label. * @param label The label of the button. */ public function DigitalButton(label:String = "") { createField(); this.label = label; buttonMode = _enabled = true; mouseChildren = false; addEventListener(MouseEvent.MOUSE_OVER, onMouseMotion); addEventListener(MouseEvent.MOUSE_OUT, onMouseMotion); } /** * Event handler for MOUSE_OVER and MOUSE_OUT events. * @param e MouseEvent. */ private function onMouseMotion(e:MouseEvent):void { if (e.type == MouseEvent.MOUSE_OVER) draw(true); else if (e.type == MouseEvent.MOUSE_OUT) draw(false); } /** * Draws the button graphics. * @param background A boolean value specifying if the button should have * a background drawn or not. */ private function draw(background:Boolean = false):void { graphics.clear(); graphics.lineStyle(2, 0x006699, 1, true, "none", "square"); if (background) graphics.beginFill(0x006699, .2); graphics.drawRect(0, 0, 140, _field.height); if (background) graphics.endFill(); filters = [new GlowFilter(0x004199, 0.8, 2, 2, 2, 10)]; _field.x = this.width * 0.5 - _field.width * 0.5; } /** * Creates the text field for the label. */ private function createField():void { _field = new TextField(); addChild(_field); var format:TextFormat = new TextFormat(); format.font = "Digital"; format.size = 24; format.color = 0x0062bd; _field.autoSize = TextFieldAutoSize.LEFT; _field.defaultTextFormat = format; _field.selectable = false; _field.embedFonts = true; _field.sharpness = 50; _field.antiAliasType = AntiAliasType.ADVANCED; } /** * The label text. */ public function set label(value:String):void { _field.text = value; draw(); } /** * The label text. */ public function get label():String { return _field.text; } /** * Specifies if the button receives mouse events. */ public function set enabled(value:Boolean):void { _enabled = value; mouseEnabled = _enabled; alpha = _enabled ? 1:0.4; } /** * Specifies if the button receives mouse events. */ public function get enabled():Boolean { return _enabled; } } }
The class is commented so you can guide yourself through it.
Step 63: Adding the Buttons
Back in the Tetris
class add three variables that will reference our buttons.
private var _startButton:DigitalButton, _pauseButton:DigitalButton, _resumeButton:DigitalButton;
Next write the addButtons()
method in which we instantiate each button, position them, and register event listeners on them.
private function addButtons():void { _startButton = new DigitalButton("Start game"); _pauseButton = new DigitalButton("Pause game"); _resumeButton = new DigitalButton("Resume game"); addChild(_startButton); addChild(_pauseButton); addChild(_resumeButton); _resumeButton.visible = false; _pauseButton.enabled = false; _startButton.x = _pauseButton.x = _resumeButton.x = _pieceDisplay.x ; _pauseButton.y = _gameCanvas.y + _gameCanvas.height - _pauseButton.height - 10; _resumeButton.y = _gameCanvas.y + _gameCanvas.height - _resumeButton.height - 10; _startButton.y = _pauseButton.y - _startButton.height - 10; _startButton.addEventListener(MouseEvent.CLICK, onStartClick); _pauseButton.addEventListener(MouseEvent.CLICK, onPauseResumeClick); _resumeButton.addEventListener(MouseEvent.CLICK, onPauseResumeClick); }
The method will be executed from within the constructor
public function Tetris() { addCanvas(); addScoreDisplay(); addPieceDisplay(); addButtons(); }
Notice that I've removed the _gameCanvas.start();
line once again because this time we'll start the game whenever the start()
button is pressed.
And the onStartClick()
, onPauseResumeClick()
event handlers are:
private function onStartClick(e:MouseEvent):void { _gameCanvas.start(); _pauseButton.enabled = true; _startButton.enabled = false; } private function onPauseResumeClick(e:MouseEvent):void { if (e.target == _pauseButton) { _gameCanvas.pause(); _resumeButton.visible = true; _pauseButton.visible = false; } else if (e.target == _resumeButton) { _gameCanvas.resume(); _resumeButton.visible = false; _pauseButton.visible = true; } }
Step 64: Final Touch
The last thing we need to do is add an event to notify listeners that the game is over.
First add a new event type in the TetrisGameEvent
event by adding a new public static constant.
public static const GAME_OVER:String = 'gameOver';
Then open the GameCanvas
class and modify the setUnits()
class like so:
private function setUnits():void { if (_currentPiece.unit1.y > 0 && _currentPiece.unit2.y > 0 && _currentPiece.unit3.y > 0 && _currentPiece.unit4.y > 0) { _grid.setUnit(_currentPiece.unit1); _grid.setUnit(_currentPiece.unit2); _grid.setUnit(_currentPiece.unit3); _grid.setUnit(_currentPiece.unit4); } else { dispatchEvent(new TetrisGameEvent(TetrisGameEvent.GAME_OVER)); stop(); } }
The logic here is, whenever a piece has units with negative y
coordinates the well (grid) has filled up and so the game has finished.
Then add an event listener for this type of event in the Tetris
class:
private function addCanvas():void { _gameCanvas = new GameCanvas(); _gameCanvas.addEventListener(TetrisGameEvent.SCORE_UPDATE, onScoreUpdate); _gameCanvas.addEventListener(TetrisGameEvent.LEVEL_INCREASE, onLevelIncrease); _gameCanvas.addEventListener(TetrisGameEvent.PIECE_UPDATE, updateView); _gameCanvas.addEventListener(TetrisGameEvent.GAME_OVER, onGameOver); addChild(_gameCanvas); }
and create the handler for the listener:
private function onGameOver(e:TetrisGameEvent):void { _startButton.enabled = true; _pauseButton.enabled = _resumeButton.visible = false; _gameCanvas.stop(); }
This was the last thing we had to add before wrapping up this tutorial.
Now you are ready to play! Test the game and see how much you can score but don't waste too much time on playing ;)
Conclusion
This ends the tutorial on how to create one of the most popular old school games: Tetris. Remember that whenever you want to recreate something or build your own game using other concepts you have to document yourself as best as possible.
Start creating piece by piece as this may be the best way to achieve a good result while having a constant overview of the progress.
I hope you enjoyed following tutorial at least as much as I did writing it.
Feel free to leave a comment with your thoughts, suggestions and questions.
Envato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post