Tag Archives: Teaching

Console Games – Catch – Part 2 (Introducing a game timer)

Based on the previous post on this, our next task is to introduce our falling objects.

This is my second go at this post, because I originally wrote it on the basis that we would introduce an actual timer into the game. On reflection, I decided against this for two reasons:
1. Timers are a difficult concept (this is aimed at teaching children to program).
2. We’re already using a rapidly iterating infinite loop, so why not use that.

Since we’re not using a timer, we’ll need to replicate a small amount of the timer functionality; Main currently looks like this:

        static void Main(string[] args)
        {
            Console.CursorVisible = false;
            DrawScreen();
            while (true)
            {
                if (AcceptInput())
                {
                    DrawScreen();
                }
            }
        }

Let’s add a timer variable into the mix:

        static void Main(string[] args)
        {
            Console.CursorVisible = false;
            DrawScreen();
            while (true)
            {
                bool autoUpdate = DateTime.Now >= nextUpdate;
                if (AcceptInput() || autoUpdate)
                {
                    DrawScreen();

                    if (autoUpdate)
                    {
                        AddStar();

                        nextUpdate = DateTime.Now.AddMilliseconds(500);
                    }                    
                }
            }
        }

That is, effectively, our timer. The AddStar method can simply add a new point at random:

        private static void AddStar()
        {
            Random rnd = new Random();
            _points.Add(new Position() { left = rnd.Next(Console.WindowWidth), top = 0 });
        }

Admittedly there’s not too much “falling” at the minute, but that can be easily addressed.

Falling Stars

So, to make the stars fall, we just need a MoveStars method; like this:

        private static void MoveStars()
        {
            for (int i = 0; i <= _points.Count() - 1; i++)
            {
                _points[i] = new Position() { left = _points[i].left, top = _points[i].top + 1 };
            }
        }

And call it from main just below AddStar():

. . .
if (autoUpdate)
{
    AddStar();
    MoveStars();

    nextUpdate = DateTime.Now.AddMilliseconds(500);
}                    
. . .

And then…

That’s it; Not exactly a ‘game’ yet – but still it looks the part. In the next and final post in this series I’ll add collision detection and keep score. I’ve uploaded this to GitHub in the same way as I did with the Snake game. Find it here.

consolecatch

Console Games – Snake – Part 5

Continuing on from my series of posts on writing a console game with my children, this post will cover the score and speed up the game a little to make it progressively harder. If you haven’t seen the earlier posts then start here.

What’s the score?

Let’s start with the score; first thing to do is create a variable to store it:

    class Program
    {
        private static int _length = 6;
        private static int _score = 0;

The way to increase the score is to eat food, so that’s quite straight-forward:

private static void DetectCollision(Position currentPos)
{
    …
    // Check if we've eaten the food
    if (_foodPosition.left == currentPos.left && _foodPosition.top == currentPos.top)
    {
        _length++;
        _score++;
        _foodPosition = null;
}

Nothing hugely complicated there. Finally, display the score:

private static void DrawScreen()
{
    Console.Clear();

    Console.SetCursorPosition(Console.WindowWidth - 3, Console.WindowHeight - 1);
    Console.Write(_score);

Speed

That’s the score; next we need to speed the game up. Currently we have an `UpdateGame()` method that determines how often the game is updated; here’s what it currently does:

        private static bool UpdateGame()
        {
            if (DateTime.Now < nextUpdate) return false;

            if (_foodPosition == null)
            {
                _foodPosition = new Position()
                {
                    left = _rnd.Next(Console.WindowWidth),
                    top = _rnd.Next(Console.WindowHeight)
                };
            }

            if (_lastKey.HasValue)
            {
                Move(_lastKey.Value);
            }

            nextUpdate = DateTime.Now.AddMilliseconds(500);
            return true;
        }

So, we can simply change the nextUpdate to use a variable that we already have; like this:

nextUpdate = DateTime.Now.AddMilliseconds(500 / (_score + 1));

Game Over

Okay, well, the eagle eyed among you may have noticed that game over just gives a runtime error; let’s try something a little more user friendly. First, we’ll create a variable to store whether the game is still in play:

        private static bool _inPlay = true;

Next, change the game loop to use this:

        static void Main(string[] args)
        {
            Console.CursorVisible = false;
            DrawScreen();
            while (_inPlay)
            {

And finally, change the `GameOver()` method:

        private static void GameOver()
        {
            _inPlay = false;
            Console.Clear();
            Console.WriteLine("Game over.");
            Console.ReadLine();
        }

Final word

I’m still working through this game, and with a catch game (which I’ll also post at some stage) with the children. The way that I’ve been addressing this is, after an initial explanation phase, asking the children to complete each small section; for example, in the above section, I would have asked them to complete three separate tasks: To create a new boolean variable, to use that variable in the while loop and to re-write the GameOver() function so that it sets the variable to false. Roughly speaking, the posts are arranged in small sections, and they could be treated as separate exercises.

Please leave a comment if you found any of these helpful, or with any suggestions for improvements.

If I get the time or the inclination, I might break these posts down into individual exercises and post that as well.

Console Games – Snake – Part 3 (Introducing a game timer)

The console snake game is progressing well. Based on where we got to on the last post, we had a game where the snake itself was behaving more or less as expected. The next task is to plant some food. In order to plant the food, we’re going to need a game timer.

What is a game timer?

It’s important to remember here that we’re using this as a teaching device, so trying to introduce something like a System.Threading timer is not going to work because it’s complicated to explain; additionally, one thing that I’ve learned from the small amount of game development that I’ve done is that the more control you have over your threads, the better. Since we already have a game loop, let’s just use that. We currently have a function to accept user input and a function to update the screen; this time we need a function to update the game variables:

        private static DateTime nextUpdate = DateTime.MinValue;
        private static bool UpdateGame()
        {
            if (DateTime.Now < nextUpdate) return false;

            nextUpdate = DateTime.Now.AddMilliseconds(500);
            return true;
        }

Notice that we have an update variable to store the next update, and return a flag where we do update. The Main function would handle this like so:

        static void Main(string[] args)
        {
            Console.CursorVisible = false;
            DrawScreen();
            while (true)
            {
                if (AcceptInput() || UpdateGame())
                    DrawScreen();                
            }
        }

So far, nothing concrete has changed. Let’s use our new function to add some `food`. This is actually quite involved, because we need to translate Position to use a class, rather than a struct; here’s why:

        private static DateTime nextUpdate = DateTime.MinValue;
        private static Position _foodPosition = null;
        private static Random _rnd = new Random();
        private static bool UpdateGame()
        {
            if (DateTime.Now < nextUpdate) return false;

            if (_foodPosition == null)
            {
                _foodPosition = new Position()
                {
                    left = _rnd.Next(Console.WindowWidth),
                    top = _rnd.Next(Console.WindowHeight)
                };
            }

            nextUpdate = DateTime.Now.AddMilliseconds(500);
            return true;
        }

We need to be able to signify that the food is nowhere (at the start, and after it’s eaten). I tried to avoid bringing in classes at this stage, because they add complexity to an already complicated change; however, this seemed the cleanest and easiest solution at this stage.

There’s some other changes to allow for the change to a class from a struct:

        private static bool AcceptInput()
        {
            if (!Console.KeyAvailable)
                return false;

            ConsoleKeyInfo key = Console.ReadKey();

            Position currentPos;
            if (points.Count != 0)
                currentPos = new Position() { left = points.Last().left, top = points.Last().top };
            else
                currentPos = GetStartPosition();

            switch (key.Key)
            {
                case ConsoleKey.LeftArrow:
                    currentPos.left--;
                    break;
                case ConsoleKey.RightArrow:
                    currentPos.left++;
                    break;
                case ConsoleKey.UpArrow:
                    currentPos.top--;
                    break;
                case ConsoleKey.DownArrow:
                    currentPos.top++;
                    break;

            }

            points.Add(currentPos);
            CleanUp();

            return true;
        }

This is because structs are immutable; meaning that we can take one, change it and add it to a collection without issue; but do that with a class and it changes the copied class.

We need to change the DrawScreen method to display the `food`:

        private static void DrawScreen()
        {
            Console.Clear();
            foreach (var point in points)
            {
                Console.SetCursorPosition(point.left, point.top);
                Console.Write('*');
            }

            if (_foodPosition != null)
            {
                Console.SetCursorPosition(_foodPosition.left, _foodPosition.top);
                Console.Write('X');
            }
        }

Finally, the snake now needs to move based on the game timer. First, refactor the section of `AcceptInput` that actually moves the snake:

        private static bool AcceptInput()
        {
            if (!Console.KeyAvailable)
                return false;

            ConsoleKeyInfo key = Console.ReadKey();

            Move(key);

            return true;
        }

        private static void Move(ConsoleKeyInfo key)
        {
            Position currentPos;
            if (points.Count != 0)
                currentPos = new Position() { left = points.Last().left, top = points.Last().top };
            else
                currentPos = GetStartPosition();

            switch (key.Key)
            {
                case ConsoleKey.LeftArrow:
                    currentPos.left--;
                    break;
                case ConsoleKey.RightArrow:
                    currentPos.left++;
                    break;
                case ConsoleKey.UpArrow:
                    currentPos.top--;
                    break;
                case ConsoleKey.DownArrow:
                    currentPos.top++;
                    break;

            }

            points.Add(currentPos);
            CleanUp();
        }

Next, we’ll just cache the key input instead of actually moving on keypress:

        static ConsoleKeyInfo _lastKey;
        private static bool AcceptInput()
        {
            if (!Console.KeyAvailable)
                return false;

            _lastKey = Console.ReadKey();

            return true;
        }

And then handle it in the UpdateGame() method:

        private static bool UpdateGame()
        {
            if (DateTime.Now < nextUpdate) return false;

            if (_foodPosition == null)
            {
                _foodPosition = new Position()
                {
                    left = _rnd.Next(Console.WindowWidth),
                    top = _rnd.Next(Console.WindowHeight)
                };
            }

            Move(_lastKey);

            nextUpdate = DateTime.Now.AddMilliseconds(500);
            return true;
        }

Next time, we’ll manage eating the food and collision detection.

GitHub

For anyone following these posts, I’ve uploaded the code so far to GitHub:

Git Hub Repository

Console Games – Snake – Part 2

Following on from this post, we were creating a snake game for the purpose of teaching a 9 year old to program. This post will not make sense without its predecessors.

Clean up the tail

The next part of this game is to get the snake game to tidy up after itself (to remove the last part of its own tail). This was done (for us) in two parts.

private static int _length = 3;        

private static void CleanUp()
{
    while (points.Count() > _length)
    {
        points.Remove(points.First());
    }
}

This is called from AcceptInput:

private static bool AcceptInput()
{
    …
    points.Add(currentPos);
    CleanUp();

    return true;
}

If you run that now, it simply does what it did before; the final part is to re-introduce the clear:

private static void DrawScreen()
{
    Console.Clear();
    foreach (var point in points)
    …

So now we have a 3 star snake game; try extending the length manually to play with it. It is strangely addictive, even in this immature state.

Console Games – Snake – Part 1

Based on this earlier post, we had a working console game. Admittedly it doesn’t do much, apart from allow you to move a star around the screen. In order to turn this into a snake game, the first thing to do is to no longer clear the screen:

        private static void DrawScreen()
        {
            //Console.Clear();
            Console.SetCursorPosition(_left, _top);
            Console.Write('*');            
        }

That gives us a snake – but you might notice that when you move left, it doesn’t ‘trail’. There is a possible workaround (albeit, not massively efficient – although remember that this is a game with the purpose of teaching programming).

First, create a struct to hold the position:

        private struct Position
        {
            public int left;
            public int top;
        }

This obviously could be a class. Next, create a list of these:

        private static List<Position> points = new List<Position>();

Here’s what the `AcceptInput` function looks like with the changes:

        private static bool AcceptInput()
        {
            if (!Console.KeyAvailable)
                return false;

            ConsoleKeyInfo key = Console.ReadKey();

            Position currentPos;
            if (points.Count != 0)
                currentPos = points.Last();
            else
                currentPos = GetStartPosition();

            switch (key.Key)
            {
                case ConsoleKey.LeftArrow:
                    currentPos.left--;
                    break;
                case ConsoleKey.RightArrow:
                    currentPos.left++;
                    break;
                case ConsoleKey.UpArrow:
                    currentPos.top--;
                    break;
                case ConsoleKey.DownArrow:
                    currentPos.top++;
                    break;

            }

            points.Add(currentPos);

            return true;
        }

        private static Position GetStartPosition()
        {
            return new Position()
            {
                left = 0,
                top = 0
            };
        }

But what about the tail

In traditional snake, the last element is removed each iteration, unless you `eat` something. Doing it the above way lends itself to this more easily. I’ll tackle that for the next post.