5,445,109 members and growing! (13,710 online)
Email Password   helpLost your password?
Languages » C# » How To     Intermediate License: The Code Project Open License (CPOL)

Falling Blocks game

By Lipman Roi

Creating the game
C#, Windows (WinXP, Windows), .NET (.NET 3.5, .NET), Dev

Posted: 6 Jul 2008
Updated: 6 Jul 2008
Views: 3,466
Bookmarked: 11 times
Announcements
Want a new Job?



Search    
Advanced Search
Sitemap
9 votes for this Article.
Popularity: 3.89 Rating: 4.07 out of 5
1 vote, 11.1%
1
0 votes, 0.0%
2
3 votes, 33.3%
3
0 votes, 0.0%
4
5 votes, 55.6%
5
Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report This Article

Download

Introduction

I’ve been looking into some design patterns on DoFactory And as a good practice I decided to write a Tetris game that uses the Factory pattern.

I’m not going to get in to details about the pattern but I would recommend reading about it.

"Abstract Factory Design Pattern.

Definition:

Provide an interface for creating families of related or dependent objects without specifying their concrete classes."

You can read about the pattern here

I assume you have all played the most famous falling blocks game 'Tetris' at some point in your life so there’s no reason to explain the game’s rules.

Starting off

When I started writing the game I didn’t actually know where to start as an objective I decided not to look at any open source falling blocks code or any tutorial on the subject I will figure out things the hard way.

So I thought a good place to start would be the simplest shape there is the cube a four by four square. But just before that we’ll have to create an abstract class named Shape which all the actual shapes will inherit from.

Just a shape

Lets have a quick look the shape’s code.

abstract class Shape
    {
        protected int turnState; // holds the current shape turn state shapes can rotate up to 4 times.
        public int TurnState { get {return turnState;} set {turnState = value;}}

        /*
         * Returns the new cords of the shape after rotation.
         */
        public abstract Point[] Turn(int top, int left);
        
        /*
         * Returns the current shape cords depends on the top left position of the shape and it's turnState.
         */
        public abstract Point[] GetCoordinates(int top, int left);
    }

As you can see shapes will be able to turn (90 degrees each time) and we can move the shape by retrieving it’s coordinates for a desired top left position. We will keep track of the shape’s top left coordinate, this will keep things generalized.

So with the Shape class in place and the idea that every shape we’ll create will inherit from it let’s move on to our square class.

Don't be a square

class Square : Shape
    {        
        public Square()
        {
        }

        public override Point[] Turn(int top, int left)
        {
            return GetCoordinates(top, left);
        }     

        public override Point[] GetCoordinates(int top, int left)
        {
            Point[] cords = new Point[4];
            cords[0] = new Point(left, top);
            cords[1] = new Point(left + 1, top);
            cords[2] = new Point(left, top + 1);
            cords[3] = new Point(left + 1, top + 1);
            return cords;
        }
    } 

Simple auh. The square doesn’t need to turn so it’s top left coordinate is always at the same place.

Shapes and their coordinates

the square labeled one is our top left position which we keep track after.

shapes.jpg

Let me clarify the GetCoordinates method.

Lets say we’d like to move our square one row down what we will do is call the GetCoordinates with Y increased by one and X remember we keep track after the top left coordinate. So by increasing Y by one we moved the square one row down

cords[0] = new Point(left, top) //is our new top left position 

based on this point we construct the rest of the square.

We still have to check if the move we just made is legit but will address this problem later on.
Once we understand this key concept of shapes representation and movement creating new shapes is easy.

Rotating a shape is just a mater of figuring out how the shape should be laid out after rotation and where our top left coordinate should go, then all that's left is reconstructing the shape based on this new top left coordinate.

turn.jpg


I wasn’t so sure about what’s the right way to turn each shape so I’ve come up with my own way.

I’ve been babbling about coordinates for a while now lets see where they actually go.

The Game’s board class

Think of the game as a two dimensional Boolean grid that has width and height, a filled space will be marked as true, Free space will be set to false.

The board class manages the game’s board by:

  • Checks if it’s possible to reposition a given shape.
  • Redraws the shape to the screen.
  • Updates the boolean values of the game board matrix.
  • Checks if any rows have been filled.

It seems logical to put all this responsibility in one place. There’s much to this class so I’ll point out only few things I find interesting:

One question that came up is How do we know if a certain shape’s move (right, left, down, rotate) is possible?

Sure we can implement a complex check for each shape but this would take too long. Fortunately there's a quicker way to do this.

Let’s have a look at the board’s class Move method

 public bool Move(Point[] currentPos, Point[] desiredPos)
        {
            if (!LegitMove(currentPos, desiredPos))
                return false;

            // Remove shape from the board
            Pen pen = new Pen(backgroundColor, 3);
            DrawShape(currentPos, pen);

            // Redraw
            RePosition(desiredPos);

            pen = new Pen(Color.Blue, 3);
            DrawShape(desiredPos, pen);
            
            return true;
        } 

The method gets the current shape position coordinates and the desired coordinates (where the shape wishes to move).

What we do within the LegitMove method is create a copy of the game’s board and “Cut out” the current shape from it so it won't take any space then we try to paste the shape to it’s new position if we succeed doing so the move is legit and we overwrite the game’s board with the copy we’ve made. Otherwise we can’t move the shape to its new location and return false indicating no changes have been made to the original game’s board.

This gives us a simple mechanism for checking all imaginable shapes moves as long as we have a way to get the current shapes position (it’s coordinates on the board) and it’s new desired position.

Putting it all together

So how do things actually work? Lets quickly go over the game “Flow”

The game asks the shape factory for a shape. More info about this class in the next section.
As far as we concerned we don’t care what the actual shape is all we know is that we’ve got a shape and we can interact with it.

Next we try to position the shape on the board at the top middle. If we failed to do that we assume the board is filled up to the top and that means the game is over.

Otherwise we set a timer that will move our shape down one row within each tick.
If the shape moved down one row successfully we do nothing.

Otherwise If the shape couldn’t move one row down then that's because it hit some other shape or it reached the game’s bottom board. We will have to check if the player had managed to fill a whole row(s), so we perform the check and update the board if needed (Clear filled rows, reposition rows above the cleared rows).

That's about it for the current shape. So Get a new shape from our factory and repeat.

During this whole process the user can manipulate the current shape by rotating, moving left, right and down, and for each “reposition” we check if the move is possible.

Hard day at the factory

class ShapesFactory
    {
        Random rand;
        enum shapes { Square, Stick, L, MirroredL, Plus, Z, MirroredZ };

        public ShapesFactory()
        {
            rand = new Random();
        }

        public Shape GetShape()
        {
            int shape = rand.Next(7);
            switch (shape)
            {
                case (int)shapes.Square:
                    return new Square();
                    break;

                case (int)shapes.Stick:
                    return new Stick();
                    break;
                
        .
        .
        .

                case (int)shapes.MirroredZ:
                    return new MirroredZ();
                    break;
                    
                default:
                    return new Square();
            }                        
        }

This class is responsible for creating new shapes based on a random number.
The factory has its products (L, cube, Z, etc’) and when the game requires a shape the factory delivers.

last words

The Game is missing some “key” features such as game boarders, letting the user know whats the next shape is going to be, displayed and keep score.

But the main core of the game is there and that was my actual goal. Also the graphics aren’t that good but I’m not a designer.

I enjoyed writing this game, it took me a while to figure out how things should work but once I got the check mechanism in place and the Shape class abstract methods, adding new shapes was surprisingly swift.

That's about wraps it, I hope I’ve pointed out some key view points on how this game works, in case you’ve got any comments, questions please feel free to write to me.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Lipman Roi


2005 - 2006 Tel Aviv university
2006 - 2007 RoboGroup
Been involved in a number of E learning projects.

2007 - 2008 ONE



Occupation: Software Developer
Company: ONE
Location: Israel Israel

Other popular C# articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 8 of 8 (Total in Forum: 8) (Refresh)FirstPrevNext
Subject  Author Date 
GeneralGood but not excellentmemberalonk7:18 29 Jul '08  
GeneralSimilar code as Google gadgetmembervirtuPIC1:23 8 Jul '08  
GeneralNew Developmentmembermikemac434417:29 7 Jul '08  
GeneralRe: New DevelopmentmemberLipman Roi20:56 7 Jul '08  
GeneralRe: New Developmentmemberalonk6:53 29 Jul '08  
GeneralRe: New DevelopmentmemberLipman Roi6:59 29 Jul '08  
GeneralRe: New Developmentmemberalonk7:26 29 Jul '08  
GeneralInterestingmemberThomas Stockwell4:11 7 Jul '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 6 Jul 2008
Editor: Chris Maunder
Copyright 2008 by Lipman Roi
Everything else Copyright © CodeProject, 1999-2008
Web16 | Advertise on the Code Project