Advertisement
  1. Game Development
  2. Game Design
Gamedevelopment

Build an AS3 Tetris Game From the Ground Up

by
Difficulty:AdvancedLength:LongLanguages:

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.

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:

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.

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:

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:

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:

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:

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:

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:


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:

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.

To see the result add the following lines of code in the Tetris class:

First add a new private variable _u

Then add the following lines of code in the constructor of the class:

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.


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:

Also create the onTimer() event handler function; in this we will increment the y property of the Unit object created earlier:

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:

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:

  1. The I shape
  2. The J shape
  3. The L shape
  4. The O shape
  5. The S shape
  6. The Z shape
  7. 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.

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.

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:


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.

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.

Note: Don't forget to add a call to the generateGroup() method in the constructor of the class just after assigning the type

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:

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:

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:


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:

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.

Also there are two more methods: the isRecent() method which tests if the current type exists in the history

and the addToHistory() method which adds the selected type to the recently spawned types array.

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

into

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.

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.

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.


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:

into

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

into

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.

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.


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).


Step 22: Starting and Stopping

Finally we need a way to control the falling as in stopping and starting it.

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.

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.

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.

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.

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

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.

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.

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.

And to make use of the MotionManager change the moveDown() method like so:

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.

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).

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.


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.

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).

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.

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.

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:

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.

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).

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.

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).

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.

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.

Next add the highlighted lines in the init() method:


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.

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.

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.

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.

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.


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).

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.

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.


Step 38: Start, Stop, Pause, Resume

Before diving deeper into the code let's add two more variables to our GameCanvas class.

  • _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.

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.


Pausing and resuming the game

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.

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.

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.


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.

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.


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.

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.

Also we'll add a getter which can be used to see if the gravity is in quick drop mode or not.

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.

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.

Lastly we need to register the keys right when the game starts (back into the start() method).

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.


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.

Also instantiate the _mainHolder in the init() method and add it to the display list.

In the landPiece we simply move all the units from the _pieceHolder into the _mainHolder

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.

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.

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().

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.


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.

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():

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.

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).

also we'll call this method whenever a new game is started.


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:

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.

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.


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.

And to set up the two fields add the highlighted lines to the class constructor:

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.

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.

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:

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:

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.

Then write the following method to instantiate and add the ScoreDisplay object to the display list.

Also add a call to this method within the constructor just after the addCanvas() line.

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:

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.

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:

As you can see this method uses a switch-case statement to calculate the score based on the number of lines and level.

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...

... and call it whenever 10 lines have been cleared more precisely in the removeFullLines() method:

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.

- and when starting the game


Step 58: ... and Listening

Now we only have to add event listeners for the corresponding events back in the Tetris class...

... and their corresponding event handler functions:

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.

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:

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:

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.

The highlighted line is used to call a method to offset the tetromino by a certain amount so that it's displayed centered.

To remove a tetromino we'll have to remove all children of the tetromino that are units.

Lastly we define a setter to set the new piece:

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:

and finally update the widget in the event handler for PIECE_UPDATE events.

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:

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.

Next write the addButtons() method in which we instantiate each button, position them, and register event listeners on them.

The method will be executed from within the constructor

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:


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.

Then open the GameCanvas class and modify the setUnits() class like so:

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:

and create the handler for the listener:

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.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.