Architecture –
The Project
All right… Finally, we are about to start the project itself. No more “fluffing”. Let’s talk about how we will develop this game.
The development of Another Block (that's the name I gave to my Tetris® clone... please don't copy it...) will be done in three stages: the engine stage, the interface stage and the fine adjustments stage. In the engine stage, we will design the game engine, with all the classes and functions that Another Block will need to implement its game. This will be the heart of the development. After that, we will design the interface, and all the programming that it needs in order to use the engine properly. After these two stages, our game will be already working, but we will still have stage three, where we will make the fine adjustments to the game, so that we make it the most fun to play.
Let’s start with the game engine. What do we need in a Tetris® game engine? Basically, we need tetraminos (that is, the Tetris® blocks, always made of four bricks), with the description of its shape, and the abilities to rotate and to be drawn in a surface, and the game itself, with the information about score, number of lines, level, current and next blocks, the pile of blocks in the game board, and the abilities to move the current block down, to rotate the current block, to move the current block to the sides, and to draw the pile and the current block in a surface. As we will see, I decided to let the timer function (the one that will control the speed of the descending block, and that will actually make the current block descend) to the interface, and not to the engine. That is a project decision, and I’ve made that decision because I wanted the engine to be made only by classes without any designer components, and a timer would be a designer component. Also, I wanted the interface to handle how the speed changes when the player advances the level, because that’s not really part of the engine… The engine has to make the game possible, but how you treat the game levels is up to the interface. That’s of course my opinion, and you can make it different, if you’d like to.
So, right now, our project looks like this:
Don’t worry too much now about the attributes and operations, but if you take a look, you will see that we basically have all the attributes and operations that are equivalent to what I described that the block and the game itself should have. Of course, the engine will have much more than that, but that we will discover a little bit later.
Let’s take a closer look in the Block class, because right now it is the most fundamental class in our game. We have only one attribute that represents the block’s shape, and that attribute is a matrix of Booleans. Why is that? Well, this is a clever way I found of representing the shape of a tetramino block. Tetris® has 7 types of blocks (Straight I, Rectangle, Corner Left L, Corner Right J, Cross T, Zigzag 1 Z and Zigzag 2 S), and they can all be represented by a matrix, where each element says if the block is filled with a brick or not. So, the blocks will be represented like this:
Straight |
Rectangle |
Corner Left |
Corner Right |
Cross |
Zigzag 1 |
Zigzag 2 |
|
Pretty cool and pretty easy, right? Ok, so that’s what our shape attribute will hold, and as you can see in the operations, there is a property Shape, because we will need to access the shape of our block from the outside (from the Game class), to check for collisions… but we will talk about that later. I am only bringing this up to talk about a good practice, which is keeping the attributes private. As you will see, all of our attributes will always be private, and if we need to access their values from the outside of the class, we create a public property to do so.
Aside from the shape attribute and property, we also have the class constructor, and a couple of methods. In the class constructor, we will do nothing right now, but later we will randomize the block type. The methods are Draw and Rotate. The Draw method will draw the block in a graphics surface, and the rotate method will rotate the block. So far, so good.
Now let’s talk about the Game class. That’s a little more complicated, because it holds the blocks, and the game board, as well as everything the game needs to be played. The game board with its pile is pretty easy; as easy as the blocks, I would say. As the blocks are Boolean matrices, the pile can be a Boolean matrix too. Its size will be the classic Tetris® board, that is, 10x20 bricks. We also have three private attributes and their correspondent read-only properties: level, score and lines. They store the current game level, the current game score, and the number of lines already completed in the game. The properties are read-only because the interface can’t change any of those, otherwise it would be cheating. :)
The game always has two blocks: the current block, and the next block. Why do we have to hold the next block, instead of creating it after the current block has hit the pile or the ground? Well, there’s one very small and simple reason: we have to show the next block to the player. That’s a classic feature in Tetris®. Aside from that, we have some methods. The class constructor will initialize everything. There are three methods that will control the current block: MoveCurrentBlockDown (surprisingly, it will move the current block down in the game board!!!), RotateCurrentBlock (it will rotate the current block) and MoveCurrentBlockSide (it will, of course, move the current block to the left or to the right). Those are the methods that correspond to the user controls. And then, we have three methods that draw the current block, the next block, and the current pile on graphic surfaces. Basically, that’s all we need, right? At least, that’s all we said we needed, to start.
Phew! This is a big article, bigger than I imagined it. I’ll stop here, and we’ll continue in the next one, where we’ll talk about the interface project. Until then!