C# - Tetris clone - Can't get block to respond properly to arrow key combinations

Daniel Waltrip picture Daniel Waltrip · May 24, 2009 · Viewed 8.2k times · Source

I'm working on programming a Tetris game in Visual C# 2005. This is the most extensive program I have designed yet.

I create a shape class and a block class to control the location, movement, and display of the different Tetris pieces. I have moveDown(), moveLeft(), and moveRight() functions for each shape (and corresponding canMoveDown(), canMoveLeft(), canMoveRight() boolean functions that verify it's ok to move). This is all working beautifully.

I want to use the down, right, and left arrow keys to let the user move the block around, in addition to using a timer to have the shape automatically fall one row every so many milliseconds.

I am using the KeyDown event handler to check when the user presses the down, left, and right arrow key. This isn't so hard. The problem is that I want to allow for diagonal motion, and I want it work as smoothly possible. I have tried a bunch of different ways of approaching this problem, with varying levels of success. But I can't get it quite right...

My most successful approach was to use three boolean variables to keep track of when the down, left, and right arrow keys are being held down. I would set the booleans to true in the KeyDown event, and to false in the KeyUp event. In the KeyDown event I would also tell the block how to move, using the boolean variables to check which combination was currently being pressed. It worked really well, except for one thing.

If I pressed one of the arrow keys and held, then pressed a second arrow key and then released the second key, the block would stop moving altogether, instead of continuing to move in the direction of the first arrow key which hasn't been released yet. I think this is because the second key triggered the KeyDown event, and upon its release the KeyUp event was fired, and the KeyDown event stopped firing completely, even though the first key is fired.

I cannot for the life me of find a satisfactory solution to this problem.

Any help would be greatly appreciated =)

Answer

BFree picture BFree · May 24, 2009

Most games don't wait for events. They poll the input device when neccessary and act accodringly. In fact, if you ever take a look at XNA, you'll see that there's a Keyboard.GetState() method (or Gamepad.GetState()) that you'll call in your update routine, and update your game logic based on the results. When working with Windows.Forms, there's nothing out of the box to do this, however you can P/Invoke the GetKeyBoardState() function to take advantage of this. The good thing about this is, that you can poll multiple keys at once, and you can therefore react to more than one key press at a time. Here's a simple class I found online that helps with this:

http://sanity-free.org/17/obtaining_key_state_info_in_dotnet_csharp_getkeystate_implementation.html

To demonstrate, I wrote a simple windows app that basically moves a ball around based on keyboard input. It uses the class I linked you to, to poll the keyboard's state. You'll notice that if you hold down two keys at a time, it'll move diagonally.

First, Ball.cs:

    public class Ball
    {
        private Brush brush;

        public float X { get; set; }
        public float Y { get; set; }
        public float DX { get; set; }
        public float DY { get; set; }
        public Color Color { get; set; }
        public float Size { get; set; }

        public void Draw(Graphics g)
        {
            if (this.brush == null)
            {
                this.brush = new SolidBrush(this.Color);
            }
            g.FillEllipse(this.brush, X, Y, Size, Size);
        }

        public void MoveRight()
        {
            this.X += DX;
        }

        public void MoveLeft()
        {
            this.X -= this.DX;
        }

        public void MoveUp()
        {
            this.Y -= this.DY;
        }

        public void MoveDown()
        {
            this.Y += this.DY;
        }
    }

Really nothing fancy at all....

Then here's the Form1 code:

    public partial class Form1 : Form
    {
        private Ball ball;
        private Timer timer;
        public Form1()
        {
            InitializeComponent();
            this.ball = new Ball
            {
                X = 10f,
                Y = 10f,
                DX = 2f,
                DY = 2f,
                Color = Color.Red,
                Size = 10f
            };
            this.timer = new Timer();
            timer.Interval = 20;
            timer.Tick += new EventHandler(timer_Tick);
            timer.Start();
        }

        void timer_Tick(object sender, EventArgs e)
        {
            var left = KeyboardInfo.GetKeyState(Keys.Left);
            var right = KeyboardInfo.GetKeyState(Keys.Right);
            var up = KeyboardInfo.GetKeyState(Keys.Up);
            var down = KeyboardInfo.GetKeyState(Keys.Down);

            if (left.IsPressed)
            {
                ball.MoveLeft();
                this.Invalidate();
            }

            if (right.IsPressed)
            {
                ball.MoveRight();
                this.Invalidate();
            }

            if (up.IsPressed)
            {
                ball.MoveUp();
                this.Invalidate();
            }

            if (down.IsPressed)
            {
                ball.MoveDown();
                this.Invalidate();
            }


        }


        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            if (this.ball != null)
            {
                this.ball.Draw(e.Graphics);
            }
        }
    }

Simple little app. Just creates a ball and a timer. Every 20 milliseconds, it checks the keyboard state, and if a key is pressed it moves it and invalidates so that it can repaint.