Tag Archives: Snake

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 4 (Collision Detection)

Collision detection is pretty much necessary for any arcade game. Super Mario would be pretty boring if he just walked through the mushrooms (or whatever they were supposed to be).

mushroom

This post continues from a little series on writing console games (start here).

In our game, we have three possibilities for collision: the wall (or edge of the console), our own tail, and food. Two of these are game over, and collecting food should make the tail longer. Let’s start with game over:

The `Move()` function currently looks like this:

        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();
        }

Off the screen

Since we’re moving anyway, this may be the best time to see where we are; since the new point is created here, let’s just see if it’s off the screen. Here’s the revised bottom of the Move command:

            }

            // Check if we're off the screen
            if (currentPos.top < 0 || currentPos.top > Console.WindowHeight 
                || currentPos.left < 0 || currentPos.left > Console.WindowWidth)
            {
                GameOver();
            }

            points.Add(currentPos);
            CleanUp();

Use Shift-Alt-F10 to get VS to generate the `GameOver()` function; which will just look like this:

        private static void GameOver()
        {
            throw new NotImplementedException();
        }

Crash into the tail

For this, we need to make a few small changes. The first is to change the _lastKey variable to look like this:

static ConsoleKeyInfo? _lastKey;

This means that we can determine whether a key has been pressed or not; next, check the collision; here’s the latest bottom of the Move function (we’ll refactor later):

                GameOver();
            }

            // Check if we've crashed into the tail
            if (points.Any(p => p.left == currentPos.left && p.top == currentPos.top))
            {
                GameOver();
            }

            points.Add(currentPos);

Just a simple Lambda to check if the head has crashed into the tail. In `UpdateGame()` the call to Move() now needs to deal with a nullable value:

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

Food

Finally, we need to detect if we’ve eaten the food; We’ll refactor the bottom of the move function and create a `DetectCollision()` function:

        private static void DetectCollision(Position currentPos)
        {
            // Check if we're off the screen
            if (currentPos.top < 0 || currentPos.top > Console.WindowHeight
                || currentPos.left < 0 || currentPos.left > Console.WindowWidth)
            {
                GameOver();
            }

            // Check if we've crashed into the tail
            if (points.Any(p => p.left == currentPos.left && p.top == currentPos.top))
            {
                GameOver();
            }

            // Check if we've eaten the food
            if (_foodPosition.left == currentPos.left && _foodPosition.top == currentPos.top)
            {
                _length++;
                _foodPosition = null;
            }
        }

All we’ve done here is checked the food position against the current one, increased the length of the tail, and then removed the food so that it will be recreated somewhere else on the screen; the `Move()` function now looks like this:

        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;

            }

            DetectCollision(currentPos);

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

Is that it?

To all intents and purposes, that is the game. It now does everything bar one feature, which is to keep score. For the final post, we’ll address this, along with the speed (it should speed up gradually to make the higher levels difficult). Also, there is a little tidying up to do.

Remember that the latest version of this is here.

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 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.