05.02 - Designing Tetris

Started by
23 comments, last by Teej 22 years, 8 months ago
Our Objectives... Alright, so here's the scenario: We're going to write our own version of Tetris, and we'll call it Blocks 'n Lines. This is a good game to emulate for a variety of reasons:
  • Everyone knows how to play Tetris, so we don't need to worry about designing the rules, gameplay issues etc., except for what we will and won't include in ours
  • The game itself is simple to write, which makes it a great introductory project
  • There isn't any AI, the physics are basic, and the graphics don't require professional rendering
The steps we're going to take in creating our game is roughly as follows:
  • take a quick overview of the game from a developer's perspective
  • decide on data structures that fit our needs
  • compile a list of features for our version of the game
  • discuss the overall organization of the game in terms of program flow
  • consider the logic necessary to implement each aspect of the game code
  • flush out the actual code implementation
  • test the program for playability, suggest possible enhancements
Ready? The Game From a Developer's Perspective Let's all take a moment and think about the game Tetris, seeing if we can start 'reverse engineering' it piece by piece, slowly but surely. The first thing that strikes me is the fact that the game takes place inside a 'play area' which sits still on the screen. It's only in this play area that anything is moving, except for perhaps the score and a picture of the next piece to be played. This is a good thing, since we don't have to worry about full-screen animation -- we can render the game over some kind of backdrop image (or just a blank screen if we wanted). Okay, so at this point I'm thinking along the lines of having bitmap images for each of the game pieces, and then I'll move the pieces downwards on a timer. It doesn't take me long to play this game mentally before I realize that at the bottom of the play area, as lines are being completed, these game pieces are being broken apart. At this point, I figure that there are three options:
  1. Create images for every possible combination of game piece, complete and broken. That's a heck of a lot of combinations of pieces, especially when you consider the fact that each complete piece can also be rotated...not a good solution at all.
  2. Draw each game piece as if they were a set of connected blocks. For instance, the straight line piece would be four blocks connected horizontally (or vertically, depending on how it's rotated), and a square piece would just be a 2x2 set of blocks. I like this idea, since it solves the problem of pieces being broken, and it's easy to think of each piece as if you were drawing it on graph paper.
  3. Draw generic 'tile' pieces that are dynamically connected to form all possible game pieces. This intuitively sounds like the correct approach visually-speaking, but adds some complexity to the code that might be excessive when we're learning how to write games; not market a new Tetris clone.
After thinking about it, I prefer the second option, at least for the time being. Admittedly, the third is the more 'polished' answer, and would be almost manditory for a professional-grade game. You see, just in thinking about drawing the game pieces, we've already ended up making some headway in how our game will ultimately be designed. This mental analysis is much better than sitting at your computer with your mind blank and drooling over the keyboard because you don't know where to start. So, let's continue theorizing on how we're going to approach writing the game... If the game pieces never broke, I'd be inclined simply to slap a couple of position variables on the active piece and be done with it, but it's not that simple. Every frame, we have to reconstruct everything perfectly, which means that we need information on every piece in memory, full or broken. Hmm, each game piece consists of exactly four blocks, so I guess that theoretically we could have about 180 pieces. How did I get that number? Well, I'm merely assuming at the moment that there are ten 'slots' across for blocks (it's either eight or ten), and somewhere around 20 levels to the play area, so assuming that each level had the maximum number of blocks in it (ten would make a complete line, so we could only have nine), thats 9x20 = 180. Of course, there's nothing stopping us from using a data structure that could hold this many block positions, but it's always wise to take a little extra time in thinking about things like this -- something better is bound to arise. If you think of the play area in terms of slots for blocks, it starts to resemble a 20x10 grid, with each position either containing a block or not. To me, anything that's a grid just screams out 'ARRAY!!! ', so let's mentally try working with a 2-dimensional array to see if it meets all of the criteria:
  • Can we draw all of the miscellaneous blocks in the play area? Absolutely; that's one of the things that makes an array ideal. Each array element can desribe either the presence or absence of a block, perhaps by using a 2D array of booleans (true or false).
  • Can we keep track of the active game piece (the one falling) in the array? Well, we could easily draw it in if we know the shape of the piece and it's position in the 20x10 play area. Here, we would just calculate which elements of the array match the shape and position of the game piece, and 'turn them on'.
  • Can we tell whether or not a piece can fall any further? In other words, has it made contact with the bottom of the play area or other blocks? Hey, that's as simple as checking values in the array.
  • Can we tell when lines are completed? This is as simple as checking the ten array elements for each level and seeing if they're all 'occupied'.
Heck, if you stare at the game long enough you can see that it's nothing more than a grid of blocks. This really becomes apparent when you have the play area 90% filled with incomplete lines, so it's not a far stretch to heavily consider the use of an array here. Two-dimensional arrays are perfect for storing co-ordinate information of the form (x,y), and you'll see them used for this purpose all of the time. Does the 2D array meet our requirements? Well, a good test is to pretend that you're the computer -- you can't see anything on the screen, but you have to provide the game to the player(s) with nothing other than data structures and variables. Can you draw each frame of animation, i.e. know the position of everything? Can you react to input by checking to see if the game piece is hitting a wall, hitting other blocks or completing a line? Can you detect completed lines and keep score? Here's how the computer might see our game:
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 1 1 1 0 0 0 0
0 0 0 0 1 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1
1 1 0 1 0 0 0 0 0 1
1 1 1 1 0 1 0 1 1 1
1 1 0 1 0 1 1 1 1 1  
Yes folks, Tetris isn't a difficult game at all when you think about it. Drawing blocks, moving pieces with timers, keeping track of score, checking for block/wall collision and reacting to completed lines is all that there is to it! The only work we have to do is keep a play area as a 2D array, and think about the logic that manipulates the array elements as the game rules dictate. Drawing each frame is a cinch. Just render the 2D array, just like with our font blitter -- 1's are blocks (blit something), and skip over 0's. If we need to 'see' what's going on in the play area, we just check values in the array, and if we need to add or remove blocks, we just alter array values. Nothing to it. The Way It Plays We don't want to get too far into the mechanics of the game; we still have to flush out our feature list. This is a learning exercise, so the KISS philosophy (Keep It Simple Stupid) applies here. Here's my first-draft:
  • seven game pieces (just like the original)
  • 20x10 play area (educated guessing)
  • one player only (KISS)
  • the active piece can move left/right/down and be rotated (90 degrees at a time)
  • completed lines are worth points, with a bonus applied to multiple lines completed simultaneously (I beleive that four at once was called a 'Tetris')
  • every certain number of completed lines, the pieces fall slightly faster (i.e. levels)
  • the game is over when the active piece can't fit in the play area; 'over the top'
  • the next piece to be played is shown to the player in advance
That's about as simple a game as you can make for a Tetris-clone. Let's try and get something up and running that conforms to this specification, and then we'll consider other tweaks and enhancements. Organization and Program Flow For such a simple game as this, there isn't much to speak of by way of organizing our game code and speculating on program flow. We've got a few entities:
  • Play Area
  • Game Pieces
  • Scoring
  • Levels
These can be managed with a small handfull of variables, so there's no need to break out the 'big guns' and add any excessive depth. What about game states? Here's a simple layout:
  • Main : 'Press a key to begin'
  • Play : Drop pieces...
  • Ready : 'Get Ready to Play...'
  • End: 'Game Over'
Once again, 'KISS in effect '. I suppose that the least we could do is write separate functions for each of the game states, and call them from Game_Main() . Anything that's common to all game states will be in Game_Main() (for instance, keyboard input and flipping the backbuffer), and each individual game state function will be in charge of what's supposed to be going on during their 'shift'. We may as well start with a clean code template, so I've added BASECODE3 to my webpage. It's basically BASECODE2 with a few slight changes:
  • Thanks to a member's advice, I've added DDSCL_ALLOWREBOOT as a flag for IDirectDraw::SetCooperativeLevel() (INITTERM.CPP -> DD_Init() )
  • I've moved and modified RestoreAllSurfaces() from INITTERM.CPP to GAMEMAIN.CPP, seeing as that's where it's used. Basically it reconstructs all of the DirectDraw surfaces and reloads our RESOURCE.BMP whenever our surface memory somehow gets lost (e.g. taken by another application). So far we've skipped all error codes from DirectX methods, but we're going to have to start taking notice soon...
  • Flip() in Game_Main() , while I'm mentioning it, attempts to restore all surfaces using RestoreAllSurfaces()
Notice how I've tricked you into accepting your next mission! If you didn't get caught, that means you have to visit my webpage -- there's a present for you... Get That 2D Feeling Your mission, and yes, you have to accept it, is to create a 2D array that will serve as the play area for our Blocks 'n Lines game, fill it with data, and render it onto the screen. The file you've just downloaded contains a bitmap with seven blocks, each in a different color. The blocks are 16x16 pixels, so that should give you some idea of the size of the play area on the display. Let's keep the resolution at 640x480, and we'll also stick with an 8-bit palettized mode (BASECODE3 takes care of this automatically -- just set the definitions in GLOBALS.H appropriately). Since there are seven different colored blocks, I'd recommend using the numbers 1-7 as array values, and 0 for 'no block'. Your rendering routine should be able to draw the correct block from the resource surface given one of these values. Also included in the workfile is a bitmap called BACKGROUND.BMP, which is a very tasteless and bland background image. Since I'm a nice guy, I'll give you a couple of definitions that will come in handy:
// Play Area Position
#define PLAYAREA_OFFSET_X   241
#define PLAYAREA_OFFSET_Y   97  
You're going to have to create a DirectDraw surface to hold this background image, which means adding some code to INITTERM.CPP in a few places (DD_Init() , DD_Term() ), as well as updating RestoreAllSurfaces() in GAMEMAIN.CPP. If you're the creative type, I'd love it if you'd put together something better than BACKGROUND.BMP and email it to me (Teej@gdnmail.net) -- if I receive any I'll add it too the official game (for educational purposes only; you retain ownership). Finally, it would be wise to place your rendering routine in it's own function, so that it can be called from elsewhere (there's no guarantee that Game_Main() will be the function needing it). All it needs to do is render what's in the 2D array, nothing more. If you are having any difficulties or have any questions, replying to this article is the perfect means of having either myself or someone else here -- your fellow colleagues -- will fix you up. Once you're done that, we'll be starting on some of the general game logic, and we'll be adding to what you've done so far. Hey, after all, it is called the 'Hands-On Interactive Game Development Tutorial'! Questions? Comments? Please reply to this topic. Edited by - Teej on July 11, 2001 11:24:15 AM
Advertisement
Teej, I think the zip of the image files doesn''t work. I know it doesn''t work for me.

"Those who are skilled in combat do not become angered, those who are skilled at winning do not become afraid. Thus the wise win before they fight, while the ignorant fight to win."
"Those who are skilled in combat do not become angered, those who are skilled at winning do not become afraid. Thus the wise win before they fight, while the ignorant fight to win."
Your right Zhuge_Liang, I''m not downloading the image zip file correctly, well I guess it''s time to get the old PSP out and design something myself.

When I find my code in tons of trouble,
Friends and colleages come to me,
Speaking words of wisdom:
"Write in C."
When I find my code in tons of trouble,Friends and colleages come to me,Speaking words of wisdom:"Write in C."My Web Site
Oops, some kind of archiving screw-up.

The file is working now.

Teej

Teej said something is this article about designing my own game...well not really my own, but kind of cool anyway. I took Spaul's Pong, my Invaders, and feverpitch's scrolling starfield and created Pong Invaders. It's a hybrid of Space Invaders and Pong as you probably guessed. Some cool sounds, level and score indicators, a start/end screen, ball finesse, some classes to manage objects (using arrays as well). Source is included so modify at will.

Some things you could add:
Work on the paddle/ball logic
Add invader animation
Add explosion animation
Make them shoot at you (ick, it's already too hard)
Put in occasional bonus dude that floats across the top

How to Play (in title screen also):
Left & Right Arrows Move Paddle
Space Launches a New Ball
Each Ball Costs 10 Points - incentive to keep ball alive
Each Invader is Worth 2 Points
The ball speeds up if it's still alive after killing 10 invaders

Get it here: http://www.geocities.com/videns/pongvaders.zip
Or here if the link isn't working: http://www.geocities.com/videns

Email me if you change it or have questions: email videns


Edited by - videns on July 7, 2001 6:34:42 PM
All right, this is killing me. Can anyone explain to me why the direct link won't work despite everything I've tried?

Edited by - videns on July 7, 2001 6:47:20 PM
EZ
videns:
All right, this is killing me. Can anyone explain to me why the direct link won''t work despite everything I''ve tried?

GeoCities blocks outside file links
http://help.yahoo.com/help/us/geo/geo-10.html
Aargh!!

Thanks Anon...the simplest answers are usually right.
EZ
Great work Teej, got my little clone flyin already. I dont want to ruin this challenge for anyone quite yet so I wont post the code for this yet, but if anyone wants it I''ll send them a copy of a top-down tile-based shooter I made with the last basecode.

It should help anyone whos stuck because the rendering is based on a 2d array of 1s and 2s.

Trying to be helpful,
Crash,

We Must Move Forwards NOT Backwards, Sideways NOT Forwards And Always Twirling Twirling Towards Success
"We Must Move Forwards NOT Backwards, Sideways NOT Forwards And Always Twirling Twirling Towards Success." - 2000"If You Keep Looking Forward Your Gonna End Up Looking Backwards At Yourself Running Sideways!" - 2001
videns: Excellent work! I was both impressed and entertained with your little game. If you''re interested, you might want to try this:
(1) Comment out the Flip() in Stars()
(2) Comment out EraseBackground() in your game-state functions (Stars() already clears the backbuffer)

Poof! Blinking all gone.

But hey, I''m not complaining... it''s a tough thing most times to start and finish a game project; even a small one, and that''s what I find most impressive.

Teej

Forgice me if i''m being dumb - but u''re homepage links dont work anymore Teej

This topic is closed to new replies.

Advertisement