Home > Game Development Walkthroughs > Coding Challenge: Write Tetris In 8 Hours or Less

Coding Challenge: Write Tetris In 8 Hours or Less


UPDATE (next day): You can read the postmortem report here!

Can I write the entire game of Tetris in 8 hours or less using my Simple2D wrapper library? Let’s find out. I’ll update this blog as I go!

(you will need the Visual C++ 2010 Runtime Redistributable to run the executable files linked below, and Windows 7 or Windows Vista SP2 with Platform Update)

15:54 – Pre-match cigarette

6 minutes to relax.

16:00 – GO GO GO!

Goal: Create project, find dimensions of Tetris grid, initialize graphics sub-systems, draw grid.

16:25 – Bucket created

A tetris bucket is 10 x 20 apparently.

Download: Source code | Executable

17:35 – Beginnings of shape management

One and a half hours in. I created a four-dimensional array of 7x4x4x4 to store the 7 possible shapes, each with 4 possible rotations, on a 4×4 grid which is essentially the bounding box for each shape. I created a class TetrisShape for this, however I have made it kind of sloppy, it is leaving most things public and the drawing routine for the shapes is actually in the rendering class rather than the shape class, conceptually better but it does mean numerous properties of the shape class have to be public, and the code is messier to boot. But we don’t have time to be elegant.

I used a 4x4x4 array for each shape because it is less messing around than writing a rotation algorithm that uses the correct (different) centre of rotation for each shape according to the original game. I will probably have to tweak the position of some of them.

I split the code up into a .cpp file and a .h file since the shape class and renderer class both reference each other.

Brush resources now have to be freed in the application rather than Simple2D as we have created an array of them dynamically, one solid colour brush for each shape type.

The game has a current shape and usually shows the next shape to fall in a box next to the bucket. The animation routine sets this up to random shapes if the game has just started (the shapes are undefined):

void SimpleTetris::UpdateObjects()
{
	// Has the game just started? If so, assign the first and second shapes (the screen shows what the next shape to drop will be)
	if (!currentShape.HasShape())
	{
		int shapeNum = rand() % NumShapeTypes;
		currentShape.SetShape(shapeLayouts[shapeNum], shapeBrush[shapeNum]);
	}

	if (!nextShape.HasShape())
	{
		int shapeNum = rand() % NumShapeTypes;
		nextShape.SetShape(shapeLayouts[rand() % NumShapeTypes], shapeBrush[shapeNum]);
	}
}

The y-position of the shape is adjusted on creation so that any empty rows in the 4×4 bounding box are conceptually above the top of the bucket. The x-position is automatically centerised.

So far then, all the shapes are configured, the first two shapes are chosen and have their positions set up correctly, and the first shape is rendered in its initial position.

Download: Source code (.cpp) | Source code (.h)Executable

19:09 – Piece movement and edge collisions

First I modified Simple2D to make a callback when WM_KEYDOWN is received from Windows. This enabled me to write the code for five movement options: left, right, down one, down to bottom (fast drop as I believe it is called) and rotate. They are all pretty similar except for rotate:

void TetrisShape::MoveRight()
{
	// First assume we can move the piece right
	PosX++;

	// Now check if it goes past the right side of the bucket
	if (isPastRight())
		PosX--;
}

Because the pieces are stored in 4×4 bounding boxes such that the edges of the piece may not be equivalent to the edge of the bounding box (making direct collision comparisons with the bounding box edges and bucket edges impossible), I wrote a bunch of helper functions. First isObjectInRow(x) and isObjectInColumn(x) to determine if row or column x in the bounding box contains a part of the piece. This can then be used by firstUsedRow(), lastUsedRow(), firstUsedColumn() and lastUsedColumn() to retrieve the relevant start and end row and column numbers of a piece, relative to the bounding box co-ordinates. These are in turn used by collision detection functions isPastLeft(), isPastRight(), isPastTop() and isPastBottom() to check if a piece is (or would be if moved) off the edge of the bucket, eg:

bool TetrisShape::isPastRight()
{
	bool collision = false;

	// Process only columns in the bounding box that are off the right-hand side of the bucket
	for (int c = PosX + 3, x = 3; c >= SimpleTetris::BucketSizeX && !collision && x >= 0; c--, x--)
		if (isObjectInColumn(x))
			collision = true;

	return collision;
}

When moving left, right or down, we only need to check the left, right and bottom sides of the bucket respectively and this is quite simple. Fast drop simply emulates moving the piece down one row at a time until it can’t go any further. Rotation however, is much trickier.

My first attempt to allow rotation, and move the piece left, right up or down if the rotated version was outside the bucket – to force it to stay inside – failed because the center of rotation of some pieces is such that doing this can lead to a situation where pieces flush against one of the four bucket edges can end up shifting one row or column if the non-rotated piece was snug against an edge and the rotated piece would not be partially outside the bucket.

Rotation is (at the moment) always allowed, and the rotation algorithm first tests the piece to see if one of its edges is against one of the edges of the bucket, then rotates it, then for each side that the piece was against, moves it so that the new rotated piece edges are aligned against the same bucket edges as prior to the rotation (this includes the top of the bucket).

Testing the four edges after rotation all use roughly the same code (this is for the left-hand edge):

	if (atLeft)
	{
		CurrentRotation = previousRotation;
		int firstOriginal = firstUsedColumn();
		CurrentRotation = (CurrentRotation + 1) % 4;
		int firstNew = firstUsedColumn();

		PosX -= firstNew - firstOriginal;
	}

No new pictures to show this time but you can now move and drop the first piece (arrow keys) and rotate it (left CTRL), so download the EXE and give it a go!

714 lines of code, 3.5 hours in. I’m getting hungry.

Download: Source code (.cpp) | Source code (.h) | Executable

19:39 – Refactoring

Some earlier parts of the code can now be re-factored using later written functions. The code which puts a new piece at the top of the grid now uses firstUsedRow() instead of a while loop to set the piece flush against the top of the bucket correctly.

I removed the four isPast*() functions and re-factored the Move functions. I also removed a lot of unnecessary code duplication in the rotation function.

The drawing of blocks is now done by the TetrisPiece class which stores a pointer (sorry, I was hoping to keep this pointer-free for the beginners) to the Simple2D rendering class. But it is much neater now 😀

And did anyone notice the mistake in my assignment of the piece type when the first ‘next shape’ is defined at the start of the game? Well I fixed that too 😉

Now we are down to 620 lines of code.

20:08 – Making the pieces move

To make the pieces move we have to update them and move them down one row each time a fixed time interval expires. In real Tetris, this interval is based on what level of the game you are on, and that is exactly what we do here. So first I’ve introduced a Level variable which for now I have just fixed at 1. The TetrisShapes now store the tick count of when they were last moved (GetTickCount() in the Windows API), and on each frame the current shape’s position is updated:

void SimpleTetris::UpdateObjects()
{
	// Skip menu (we haven't implemented it yet) and start a new game if we aren't playing yet
	if (GameState == 0)
	{
		GameState = 1;
		NewGame();
	}

	if (currentShape.Update(Level))
	{
		NewPiece();
	}
}

...

// Move the piece down one place if enough time has elapsed
bool TetrisShape::Update(int level)
{
	bool finished = false;

	// We will move the piece down at a rate of 1 row per second for level 1, decreasing by
	// 0.05 seconds on each additional level

	// TickCount measures time in ms

	int lifetime = 1000 - ((level - 1) * 50);

	if (GetTickCount() >= LastMoveTime + lifetime)
	{
		LastMoveTime = GetTickCount();
		finished = MoveDown();
	}

	return finished;
}

...

// Set up a new game
// Note this has to be called AFTER the brush resources are made
void SimpleTetris::NewGame()
{
	int shapeNum;

	// Assign the first and second shapes (the screen shows what the next shape to drop will be)
	shapeNum = rand() % NumShapeTypes;
	currentShape.SetShape(shapeLayouts[shapeNum], shapeBrush[shapeNum]);
	currentShape.Activate();

	shapeNum = rand() % NumShapeTypes;
	nextShape.SetShape(shapeLayouts[shapeNum], shapeBrush[shapeNum]);

	// You are on level 1
	Level = 1;
}

// Set up a new piece
void SimpleTetris::NewPiece()
{
	// Bring the next shape into play
	currentShape = nextShape;
	currentShape.Activate();

	// Create a new 'next shape'
	int shapeNum = rand() % NumShapeTypes;
	nextShape.SetShape(shapeLayouts[shapeNum], shapeBrush[shapeNum]);
}

...

// Bring a piece into play
void TetrisShape::Activate()
{
	LastMoveTime = GetTickCount();
}

Right now, when a piece reaches the bottom it will just vanish because we haven’t made a place to store the contents of the bucket (game board) yet… and that is what I shall do next!

Download: Source code (.cpp) | Source code (.h) | Executable

21:44 – Maintaining the board

Whew this took quite a bit longer than I expected. I made a perhaps questionable design decision to re-factor the code such that the bucket was its own class and so were individual piece types, on the one hand clearing up a fair bit of array mess but on the other hand introducing perhaps unnecessary complexity. I never meant to make this heavily OOP and I’ve made a point of avoiding the use of things like operator overloading and dynamically allocated objects (wherever possible) to keep it simple for the beginning game programmers!

I also spent a little while now battling a crash-on-exit bug which may still be present in this EXE – I couldn’t reproduce it in the end, so let me know if it stings you.

So two new classes are introduced: TetrisPieceType of which 7 are created at the start of execution, each containing full information about a specific type of piece: its layout in all rotations, its colour (Direct2D paintbrush resource) and an index that makes it easy to identify. The second class is TetrisBucket which has the game board itself.

At the start of the game, all the bucket rows and columns are set to blank which I’ve represented as -1 (minus 1) in the code. When a piece can fall no further (or you use fast drop), it is written into the bucket; that is to say, the non-blank portions of the piece type’s bounding box contents are written into the bucket. The bucket is stored as a 2-dimensional array of ints. To make sure all previously dropped shapes get re-drawn on subsequent frames with the correct colour, each grid square in the bucket is associated with the index number of the piece type which fell there. The actual shape is discarded and the bucket is just a jumble of different coloured blocks that aren’t connected to each other.

Once the current piece has been written, it is discarded, the next piece is set to the current piece and a new piece is generated as the new ‘next piece’.

This version also draws the next shape in a box in the top-right corner of the window, which required implementing a new drawing routine that could centerize a piece in a bounding box.

I had to make a small modification to Simple2D to allow the resolution (width and height) of the window to be accessed outside the class object.

There is no collision detection on the actual pieces (bucket contents) yet so enjoy this easy game while you can 🙂

Two hours to go: I’m feeling the time pressure now, it’s sweltering hot in here, getting dark and I really need a drink, a smoke and some food!

Download: Source code (.cpp) | Source code (.h) | Executable

22:22 – Collision detection

This was quicker than I expected so I’m more or less back on schedule now. The collision detection is working; I was a bit cheeky and just disallowed rotation if it collided with another piece on the board; in reality the piece might get shifted a little bit but this is for all intents and purposes an unusual edge case scenario, and real Tetris also disallows the majority of such rotations.

Next up I have to detect when lines have been filled, remove them, keep and display your score, and handle the situation where the grid gets full.

Download: Source code (.cpp) | Source code (.h) | Executable

23:40 – ARGH Text damn you DirectWrite

Well I had to add to my Simple2D library some text processing functions because there weren’t any and I have zero experience with that in Direct2D, so there was a lot of MSDNing and Googling, I plumbed the ridiculous DirectWrite semantics in as best I could and reduced this monstrosity on Microsoft’s documentation site to something more like this:

textFormat = MakeTextFormat(L"Courier New", 18.0f, DWRITE_FONT_WEIGHT_BOLD);
textBrush = MakeBrush(Colour::Snow);
Text(50, 60, "Score", textFormat, textBrush);

Yes, I really prefer mine better to be honest. Anyhow, that wasted a lot of time, I threw in the code to check for the game over condition (if the next piece already collides with something on the grid when it is first brought into play), and the gameState now has three options: 0 for no game started yet, 1 for game in progress, and 2 for game over. This should probably be re-written as an enum.

15 minutes left, no time to chat!

Download: Source code (.cpp) | Source code (.h) | Executable

23:59 – DONE IT!!!

Scores, level, line clearing, grid moving when line is cleared, and bonus scores for multiple lines added. That … was CLOSE!

Read the postmortem (written the following day).

Download: Source code (.cpp) | Source code (.h) | Executable

I’m a software developer with very limited work capacity due to having the debilitating illness M.E. – please read my article Dying with M.E. as a Software Developer and donate to the crowdfund to help me with my bucket list if you found this article useful. Thank you so much!

  1. Ahmad Shakeel
    June 9, 2015 at 14:06

    where i can download Simple 2D library?

  2. April 18, 2015 at 03:16

    Hmmm, Very cool….however, I get a few functions as undefined when I compile it, if I uncomment them it works, but the keyboard does not work. The exe downloads and runs properly though.
    Here are the lines I have to uncomment as the functions are undefined: (same in asteroids btw)
    void Simple2DStart()
    {
    SimpleTetris tetris;
    // tetris.SetWindowName(L”SimpleTetris by Katy Coe (c) 2012″);
    // tetris.SetResolution(640, 480);
    tetris.SetBackgroundColour(Colour::Black);
    // tetris.SetResizableWindow(false);

    tetris.Run();
    }

    Any ideas? Did the Simple2D library ever have these functions?

    I suspect my keyboard issues may be related to these functions being missing…as I say, I comment these 3 lines out, and the game actually runs, just no keyboard
    Thanks!
    Awesome site, looking for simple games to teach elementary school kids coding!
    Eric

  3. Sumedh Vijay
    August 13, 2014 at 23:09

    Can I look at your main()? How are you implementing the game?

  4. April 10, 2014 at 18:31

    very good , and interesting project, you should have what the keys you use , because if we do not see the source we never would have guessed wich one is for rotating

  5. Adalberto Felipe
    March 22, 2014 at 21:33

    Congratulations, Katy!

  6. September 17, 2012 at 07:24

    Very helpful. 3ks.

  7. Jez
    May 23, 2012 at 21:16

    I never doubted you for a minute. Colours kinda suck though. 😉

  8. May 22, 2012 at 19:17

    Great idea. Keep up the good work.

  1. March 15, 2013 at 15:16
  2. January 18, 2013 at 21:23
  3. January 16, 2013 at 18:32
  4. October 13, 2012 at 04:52
  5. October 12, 2012 at 00:17
  6. September 29, 2012 at 21:51
  7. September 22, 2012 at 21:17
  8. June 13, 2012 at 13:41
  9. June 3, 2012 at 02:29
  10. May 23, 2012 at 17:56
  11. May 23, 2012 at 16:49

Share your thoughts! Note: to post source code, enclose it in [code lang=...] [/code] tags. Valid values for 'lang' are cpp, csharp, xml, javascript, php etc. To post compiler errors or other text that is best read monospaced, use 'text' as the value for lang.

This site uses Akismet to reduce spam. Learn how your comment data is processed.