Gigi Labs

Please follow Gigi Labs for the latest articles. Programmer's Ranch no longer has its domain, so please update your bookmarks and links to programmersranch.blogspot.com.

Thursday, May 16, 2013

C# Threading: Bouncing Ball

Hi all! :)

I hope you've enjoyed the articles so far, and found the little ASCII art games both entertaining and mentally stimulating. Towards the end of yesterday's article, C# Basics: Snake Game in ASCII Art (Part 2), we discussed the problems with the Snake game. One of them was that the snake wouldn't move on its own - you had to press a key in order to make the program do something.

In today's article, we're going to begin learning about threads, and use them to overcome this limitation. This time we're going to draw a ball bouncing all over the console window.

Start a new console application, and add the following struct before the class Program:

    struct Vector
    {
        public int X;
        public int Y;
       
        public Vector(int x, int y)
        {
            this.X = x;
            this.Y = y;
        }
    }

You'll notice this is exactly the same as the Location struct in C# Basics: Snake Game in ASCII Art (Part 1). Instead of just using it just as a location, we're also going to use it as a direction. You'll see what I mean in a minute. Add the following for starters:

            Console.OutputEncoding = System.Text.Encoding.GetEncoding(1252);
            Console.Title = "Bouncing Ball";
           
            Vector ballLocation = new Vector(4012);
            Vector direction = new Vector(-1, -1);

So ballLocation is the location in the console window where we're going to draw our ball. The direction represents where the ball will go next, relative to ballLocation. In this case, the ball starts at (40, 12) and moves in a northwest direction, so the next position will be (39, 11), and then (38, 10), and so on.

[This paragraph is a bit more advanced... feel free to skip it.] The Vector struct is simply a code representation of a mathematical vector, where a vector such as (3, 1) means "three steps to the right, one step down" (depending on what coordinate system you're using). I'm not going to get into the mathematics, but if you're interested, check out my "A Concise Introduction to Vectors" [PDF].

We're now going to add a loop to show the ball and then move it:

            while (true)
            {
                Console.Clear();
                Console.SetCursorPosition(ballLocation.X, ballLocation.Y);
                Console.Write((char4);
               
                ballLocation.X += direction.X;
                ballLocation.Y += direction.Y;
                Thread.Sleep(100);
            }

At the beginning, add the following which is needed by Thread:

using System.Threading;

The code above is an infinite loop; there no condition for which the while loop will end. In this loop, we show an ASCII diamond as our ball, and then simply add the direction to the ballLocation in order to move it. The Thread.Sleep() at the end is simply a delay (in milliseconds) so that you can see the ball move; otherwise it would move quickly. Try changing the value of 100 and use something else (e.g. 1000, which means 1 second) to see what happens.

Press F5 to see the ball move:


You will get an exception once the ball moves off the edge. Add the following to fix this and make the ball change direction when it hits an edge (note || means "OR"):

                if (ballLocation.X == 0 || ballLocation.X == 79)
                    direction.X = -direction.X;
                if (ballLocation.Y == 0 || ballLocation.Y == 24)
                    direction.Y = -direction.Y;

The infinite loop allows the ball to move on its own without user intervention - just as we wanted with Snake. However, there is a problem: we cannot accept any user input like this. In fact, you can only exit by actually closing the window. If you try to add a

Console.ReadKey(true);

at the end of the while loop, we're back to square one: the user must press a key for anything to happen. Clearly, in order to accept input, we need the program to do two things at the same time: let the ball move, and handle user input. Fortunately, such godlike powers are not unique to Chuck Norris and Multiple Man.

A program (or process to be more precise) can do several things at the same time. It can be composed of multiple threads which run at the same time but do different things. This area of programming is called multithreading. Let's see how we can exploit this to allow user input while the ball is moving on its own.

First, move the while loop and the vectors to a new method outside of Main():

        public static void Move()
        {
            Vector ballLocation = new Vector(4012);
            Vector direction = new Vector(-1, -1);
           
            while (true)
            {
                Console.Clear();
                Console.SetCursorPosition(ballLocation.X, ballLocation.Y);
                Console.Write((char4);
               
                ballLocation.X += direction.X;
                ballLocation.Y += direction.Y;
                Thread.Sleep(100);

                if (ballLocation.X == 0 || ballLocation.X == 79)
                    direction.X = -direction.X;
                if (ballLocation.Y == 0 || ballLocation.Y == 24)
                    direction.Y = -direction.Y;
            }
        }

In what remains of the Main() method, add the following code:

            Thread thread = new Thread(Move);
            thread.IsBackground = true;
            thread.Start();
           
            Console.ReadKey(true);

When you press F5 and run the program, you will now end up with two threads:


The new thread is created in the first line of code above. A thread executes the code in a particular method, so we supply the name of the Move() method (without brackets) as a parameter.

In the second line, we set thread.IsBackground = true. A process (running instance of a program) runs as long as there is a foreground thread (such as the main thread) running. Since our new thread is designated as a background thread, the process will terminate once the main thread has exited.

The rest of the Main() method then starts the secondary thread and waits for a keypress. When the keypress is received, Main() ends, and with it the process. If we didn't set the secondary thread as a background thread, the process would keep on running anyway. Try that out. :)

Fantastic. :) In this article, we learned how to use a simple thread in order to allow a program to do two things at the same time. We used this to allow a game to run, while at the same time waiting for user input.

Stay tuned for more! :)

No comments:

Post a Comment