Category Archives: Game Development

Advancing to the next Level in Unity

I’m quite new to Unity, and so thought I would start blogging useful things that I’ve discovered. Obviously, there is the which is basically where all this information comes from, and also an excellent tutorial on YouTube. I found this after trying a Pluralsight course on Unity, but rapidly losing the will to live while watching it.

In my particular example, I have a game, whereby touching a specific object progresses to the next level. Here’s the code (against the target object):

    void OnCollisionEnter()
    {
        if (Application.loadedLevel < Application.levelCount)
            Application.LoadLevel(Application.loadedLevel + 1);
        
    }

I admit, it’s not exactly rocket science, but it took me a while to work this out. I spent some time trying to parse the level name, and other workarounds, before I spotted that it’s all in the Application object.

Handling Input in XNA / MonoGame

This morning I didn’t have internet access and was trying to find this in my notes (which are also my blog posts), and I couldn’t; so purely for my own benefit, the following is a method of handling touch or mouse input in Monogame:

        public void HandleInput()
        {
            ProcessMouseInput();
            ProcessTouchInput();
            ProcessKeyboardInput();
        }

        private void ProcessMouseInput()
        {
            var currentMouse = Microsoft.Xna.Framework.Input.Mouse.GetState();

            if (currentMouse.LeftButton == Microsoft.Xna.Framework.Input.ButtonState.Pressed)
            {
                HandlePressed(currentMouse.Position, _controlledObject);
            }
        }

        private void ProcessTouchInput()
        {
            var currentTouch = Microsoft.Xna.Framework.Input.Touch.TouchPanel.GetState();

            foreach (var touch in currentTouch.Where(t => t.State == Microsoft.Xna.Framework.Input.Touch.TouchLocationState.Pressed))
            {
                HandlePressed(touch.Position, _controlledObject);
            }


        }

        private void ProcessKeyboardInput()
        {
            var currentKeys = Microsoft.Xna.Framework.Input.Keyboard.GetState();
            if (currentKeys.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Left))
            {
                _handler.GoLeft(_controlledObject);

By changing Pressed to Released, you can detect when the mouse button click / touch finished; however, if you hold your finger / mouse button down, it will only return a single result.

Enabling Consumable Purchases in Windows Store Apps

What are consumables

Consumables are a type of in-app purchase that can be used within your app or game; by used, I mean, for example, coin, food, life-force, credit; anything that can be bought and then the same item be bought again. This is opposed to durables, which are in-app purchases, such as removing adverts, premium features, etc.

Where to go first

The MSDN article does cover most of what you need. However, it doesn’t seem to cover everything, hence this post.

The documentation for the CurrentAppSimulator is also useful.

The Principle

Actual purchasing is done through the CurrentApp class. However, there is an identical test version of this, which simulates the purchasing of in-app products. Part of the store certification process is to ensure you haven’t forgotten to switch these to their live counterparts; although using the #Debug and #Release configurations might be an idea, too (see the bottom of this post for more details on this).

Step 1 – WindowsStoreProxy.xml

When you run your application in real life, it will download the purchase information from the store. However, when you’re testing, you need to simulate this. The linked documents do have examples; however, IMHO, they don’t completely explain the implications of each section. Here’s an XML file:

<?xml version="1.0" encoding="utf-16" ?>
<CurrentApp>
  <ListingInformation>
    <App>
      <AppId>988b90e4-5d4d-4dea-99d0-e423e414ffbc</AppId>
      <LinkUri>http://apps.windows.microsoft.com/app/988b90e4-5d4d-4dea-99d0-e423e414ffbc</LinkUri>
      <CurrentMarket>en-gb</CurrentMarket>
      <AgeRating>12</AgeRating>
      <MarketData xml:lang="en-gb">
        <Name>App with several in-app products</Name>
        <Description>Sample app for demonstrating an expiring in-app product and a consumable in-app product</Description>
        <Price>0.00</Price>
        <CurrencySymbol>£</CurrencySymbol>
      </MarketData>
    </App>
    <Product ProductId="MORE_CASH_1000" LicenseDuration="0" ProductType="Consumable">
      <MarketData xml:lang="en-gb">
        <Name>Consumable Item</Name>
        <Price>0.99</Price>
        <CurrencySymbol>£</CurrencySymbol>
      </MarketData> 
    </Product>
  </ListingInformation>
  <LicenseInformation>
    <App>
      <IsActive>true</IsActive>
      <IsTrial>false</IsTrial>
    </App>
  </LicenseInformation>
  <!--
  <ConsumableInformation>
    <Product ProductId="MORE_CASH_1000" TransactionId="00000001-0000-0000-0000-000000000000" Status="Active" />
  </ConsumableInformation>
  -->
</CurrentApp>

It looks a lot like the MS example, with a few key differences: firstly, it only contains a single consumable; second, it’s in GBP; and thirdly, the “ConsumableInformation” is commented out. The single consumable is just because that’s what I’m working with, but the other two burnt me:

  • If you change the language or currency, you need to be consistent. I left an en-us in and it, point blank, refused to read the document. I spent a while checking the XML was the correct format, and finally just guessed at this.
  • The ConsumableInformation node is commented out. If you put it in, then when you read the license, it will tell you that it is unfulfilled. This is definitely useful for testing, but looks like a bug in your code if you don’t know this.

Store this in a Data folder within the project:

consumables1

Step 2 – Create a helper class for managing the purchase

Obviously, this isn’t a requirement; but I would create a class for each consumable purchase. If you have common code then create a helper and base class as well.

namespace BetRaces.Purchases
{
    public class Purchase
    {
        public const string MORE_CASH_PRODUCT = "MORE_CASH_1000";
        public const int MORE_CASH_AMOUNT = 1000;

The following steps are building on the existence of such a class.

Step 3 – Create a dictionary of purchased GUIDs

The idea here is that you can track what has been bought.

private Dictionary<string, List<Guid>> grantedConsumableTransactionIds;

Step 4 – Grant Feature Locally

If you read the linked documents, they suggest a version of this function; basically, you need a function that will perform the task that you’ve asked for. In this case, it will manage the purchase of the coins, time, bonus, whatever. The following code is pretty much an exact duplicate of that offered by MS:

        private async void GrantFeatureLocally(string productId, Guid transactionId)
        {
            if (grantedConsumableTransactionIds == null)
                grantedConsumableTransactionIds = new Dictionary<string, List<Guid>>();

            if (!grantedConsumableTransactionIds.ContainsKey(productId))
            {
                grantedConsumableTransactionIds.Add(productId, new List<Guid>());
            }
            grantedConsumableTransactionIds[productId].Add(transactionId);

            // Grant the user their content. You will likely increase some kind of gold/coins/some other asset count.
            App.settings.CashPot.Total += MORE_CASH_AMOUNT;
            App.settings.SaveSettings();  // Ensure that the purchase is saved before reporting it as successful.
            FulfillmentResult result = await CurrentAppSimulator.ReportConsumableFulfillmentAsync(MORE_CASH_PRODUCT, transactionId);

        }

Step 5 – Get Unfulfilled Consumables

The reasoning here is that you have started to make a purchase, but the line above `ReportConsumableFulfillmentAsync` has not been called. This then sits in a status which blocks future purchases.

        private async Task GetUnfulfilledConsumables()
        {
            var products = await CurrentAppSimulator.GetUnfulfilledConsumablesAsync();

            foreach (UnfulfilledConsumable product in products)
            {
                GrantFeatureLocally(product.ProductId, product.TransactionId);
            }
        }

Obviously, there is a risk that the code in step 4 will crash just at the point before you report the fulfilment; however, I’d rather that, than the user having paid for something they haven’t received.

Step 6 – Purchase

The next stage is a RequestProductPurchase() method; here’s the code:

        public async Task<bool> RequestProductPurchase(string productId)
        {
            Uri uri = new Uri("ms-appx:///Data/WindowsStoreProxy.xml");
            Windows.Storage.StorageFile storeProxy = await StorageFile.GetFileFromApplicationUriAsync(uri);

            await CurrentAppSimulator.ReloadSimulatorAsync(storeProxy);
            
            Guid product1TempTransactionId;

            PurchaseResults purchaseResults = await CurrentAppSimulator.RequestProductPurchaseAsync(productId);
            if (purchaseResults == null) return false;

            switch (purchaseResults.Status)
            {
                case ProductPurchaseStatus.Succeeded:
                    product1TempTransactionId = purchaseResults.TransactionId;

                    // Grant the user their purchase here, and then pass the product ID and transaction ID to currentAppSimulator.reportConsumableFulfillment
                    // To indicate local fulfillment to the Windows Store.
                    GrantFeatureLocally(productId, product1TempTransactionId);
                    return true;

                case ProductPurchaseStatus.NotFulfilled:
                    product1TempTransactionId = purchaseResults.TransactionId;

                    // First check for unfulfilled purchases and grant any unfulfilled purchases from an earlier transaction.
                    await GetUnfulfilledConsumables();

                    // Once products are fulfilled pass the product ID and transaction ID to currentAppSimulator.reportConsumableFulfillment
                    // To indicate local fulfillment to the Windows Store.
                    if (grantedConsumableTransactionIds != null && grantedConsumableTransactionIds.ContainsKey(productId))
                        return true;
                    return false;                    
            }

            return false;
        }

The above code is what I was referring to in Step 1, when I mentioned the NotFulfilled return status.

Step 7 – Test the change

When you try to make a purchase, you should see a screen such as this:

consumables2

You can then test possible eventualities.

Step 8 – Enable them in the store

The next step is to enable your purchases in the App Store. The code has to be the same; and for larger games (the producers of which will probably not be reading posts such as this) the codes will be generated on a server, so they can manage special offers, etc. centrally.

In the Services section of the dashboard:

consumables3

Now enter your offer code, along with the price and make it a “Consumable”:

consumables4

Step 9 – The Old Switcheroo

All you need to do now is to substitute CurrentAppSimulator for CurrentApp.

Because both classes are static, I couldn’t find a better way than this:

#if DEBUG
            PurchaseResults purchaseResults = await CurrentAppSimulator.RequestProductPurchaseAsync(productId);
#else
            PurchaseResults purchaseResults = await CurrentApp.RequestProductPurchaseAsync(productId);
#endif

Windows Store Apps automatically compile in Release mode for the store.

Once you’ve found all the CurrentAppSimulator references and replaced them with this conditional construct (by my count there are 4 places for this); you should see the following when you try to make the purchase:

consumables5

consumables6

Conclusion

The links at the start are by far the best resource available for this; but hopefully this will fill in a couple of the gaps that tripped me up.

Adding and Using a New Font in MonoGame (The 11 Step Program)

This is not an undocumented subject; however, I didn’t find everything I needed in a single place; so this is my single point of reference.

The Problem

There is currently no facility within MonoGame to create a new Sprite Font. The workaround described below is, to put it mildly, time consuming.

Adding a Font

Step 1

The first step is to download Visual Studio 2010

This is the only download link to the express edition (obviously if you have an MSDN license, you can get the full edition. VS Express has now been replaced by the Community Edition, but that’s VS2013.

Step 2

Next, you need to download XNA Game Studio

… and install it.

Step 3

Now, load up VS2010 and create a new XNA game:

MonoGame1

You should end up with a project that looks like this:

MonoGame2

Step 4

In the Content Project, add a new file:

MonoGame3

… and add a new SpriteFont:

MonoGame4

Step 5

If you now open the file, you can edit key aspects of the font, such as size, font name, etc…:

MonoGame5

Step 6

To test, make the following changes in the main project (Game1.cs):

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
            spriteBatch = new SpriteBatch(GraphicsDevice);
            Font1 = Content.Load<SpriteFont>("SpriteFont2");
            FontPos = new Vector2(graphics.GraphicsDevice.Viewport.Width / 2, graphics.GraphicsDevice.Viewport.Height / 2);

        }
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();
            string output = "abcdefghijklmnopqrstuvwxyz!";
            Vector2 FontOrigin = Font1.MeasureString(output) / 2;
            spriteBatch.DrawString(Font1, output, FontPos, Color.Black,
                          0, FontOrigin, 1.0f, SpriteEffects.None, 0.5f);
            spriteBatch.End();

            base.Draw(gameTime);
        }

Step 7

Run the project:

MonoGame6

Step 8

Okay, now you have a font (the font was compiled when you ran the project in the previous step). Locate the compiled font:

MonoGame7

Step 9

Copy the compiled font to your MonoGame content directory.

Step 10

Set the properties of the font to be Content:

MonoGame8

MonoGame9

Step 11

You’re done, you can now use it in your project in the same manner as you did in the test project earlier.

Conclusion

Work is underway to incorporate this into MonoGame. However, it’s still a massively painful process!

References

Below are some useful links that I found (I’d be happy to add more, or just leave a comment):

http://www.c-sharpcorner.com/uploadfile/iersoy/how-to-use-spritefont-in-xna/

http://gamedev.stackexchange.com/questions/46128/how-to-generate-spritefonts-for-monogame

http://stackoverflow.com/questions/18268413/loading-a-font-using-monogame-with-vs2012-13

Using Asynchronous methods within a Windows Game

The new async / await syntax in .NET 4.5 + makes asynchronous programming really easy. However, there are times when having an assumption of asynchrony can impede you. One such example is programming for games (see my post on why you might want to avoid this).

However, what happens when you want to display a windows message box, or some other action that is asynchronous; See my post on a message box helper for an example.

In my particular case, I was to show a message box asynchronously, and perform a certain action based on the result; however, I don’t want to stop the game, and I don’t want to have to introduce an async / await into the programming model (for reasons in the linked post).

My solution was to use a combination of two, slightly outdated, methods of asynchronous programming: call backs and continuation blocks (strictly speaking, async / await does use continuation blocks behind the scenes admittedly). The following code will attempt to make an in-app purchase from the store:

        internal static async Task<bool> PurchaseCash()
        {
            var result = await Windows.ApplicationModel.Store.CurrentApp.RequestProductPurchaseAsync(PURCHASE);
            return (result.Status == ProductPurchaseStatus.Succeeded);
        }

What that function actually does it not important; however, it needs to be called from within a game loop. Here’s how it is called:

                        Purchase.PurchaseCash().ContinueWith((purchaseTask) =>
                        {
                            purchaseTask.Wait();
                            if (purchaseTask.Result)
                            {
                                App.settings.CashPot.Total += Purchases.Purchase.MORE_CASH_AMOUNT;
                            }

                        });

This will only execute the purchase action if the purchase was successful; it’s completely asynchronous, and it doesn’t affect the main thread. All well and good, but what if, instead of a specific task, we wanted to execute a conditional command; for example: when the purchase is called, we want to turn on a specific feature.

In this case, I decided to use a call back; the method signature looks like this:

        private bool MakePurchase(int cost, Action onSuccess)

And it is called like this:

                    if (!App.settings.Purchase1)
                    {
                        MakePurchase(PURCHASE1_COST, () =>
                        {
                            App.settings.Feature1 = true;
                        });
                    }

Inside MakePurchase, I only call the onSuccess method where the purchase was successful:

                        Purchase.PurchaseCash().ContinueWith((purchaseTask) =>
                        {
                            purchaseTask.Wait();
                            if (purchaseTask.Result)
                            {
                                onSuccess.Invoke();
                            }
                        }

Conclusion

The syntax above is nowhere near as clear and concise as a simple await statement; however, await statements can’t be used outside of an async method and, especially when programming games, that’s not always practical. The other thing that I haven’t mentioned here is exception handling – I may make that the subject of a later post.

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 – Catch – Part 1

I’ve written a series of posts based on teaching programming to children (specifically my 9 year old children). Currently, we’ve managed to produce a snake game, but we’re also working on a “Catch” game. This is a game whereby things drop from the top of the game screen, and the player must “Catch” them.

Before starting, it’s worth refering back to my first post for the basis of the game.

The initial set-up is the same; the difference for this game will mainly be that the player can only either move left, or right:

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

    ConsoleKeyInfo key = Console.ReadKey();

    switch (key.Key)
    {
        case ConsoleKey.LeftArrow:
            _left--;
            break;
        case ConsoleKey.RightArrow:
            _left++;
            break;
    }

    return true;
}

Additionally, I’ve used a more bucket-like drawing for this game:

private static void DrawScreen()
{
    Console.Clear();
    Console.SetCursorPosition(_left, _top);
    Console.Write(@"\_/");
}

The main function and variables look like this still (the only change being the default for top, which should resolve to the height of the screen – 0, 0 being the top left):

private static int _left = 0;
private static int _top = Console.WindowHeight - 1;

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

So, now we have a basis, the “bucket” moves along the bottom of the screen. The next task is to introduce the “falling things”.

Reading NumPad keys using WinRT

Recently, I discovered a bug with one of my Windows Store games – Maths Races. The issue was that when a number key was pressed, the program works fine, but pressing a number pad key – even with NumLock on wasn’t recognised.

Here’s the code that I was using the detect the keypress:

KeyboardState newState = Keyboard.GetState();
var keys = newState.GetPressedKeys();

foreach (var key in keys)
{
    if (!IsNewKeyPressed(key))
        continue;

    byte keychar;
    if (byte.TryParse(key.GetHashCode().ToString(), out keychar))
    {
        char newChar = Convert.ToChar(keychar);

        if (Char.IsNumber(newChar))
        {
            . . . 

So, when I press a number pad key with Num Lock off, it behaves as though I’d pressed a different character; for example NumPad0 resolves to a key character code of 96:

(byte.TryParse(key.GetHashCode().ToString(), out keychar))

Which when converted to a char is “`”.

Okay, so here’s how I got around this:

if (key.HasFlag(Keys.NumPad0 & Keys.NumPad1 & Keys.NumPad2 &
    Keys.NumPad3 & Keys.NumPad4 & Keys.NumPad5 & Keys.NumPad6 &
    Keys.NumPad7 & Keys.NumPad8 & Keys.NumPad9))
{
    char number = key.ToString().Substring(6)[0];
    . . .
}                
else
   . . .

Admittedly it isn’t very pretty, and if someone knows a better, or more elegant way to do this, then please let me know.

MVVMCross – Overriding The Default Plugins

Following on from this post I discovered that I did, indeed, have a recursive reference in my game file. After a little searching, I found this excellent article on how to prevent this error, and to make the Json serializer behave rationally.

In MVVMCross, the code to serialize JSON is based on a standard plugin; it looks like this (or at least this is how I am saving my game):

public void Save()
{
    var jsonConv = Mvx.Resolve<IMvxJsonConverter>();            
    string text = jsonConv.SerializeObject(this);
    FileHelper.SaveGameFile(text);
}

The type is registered in App.cs like this:

Mvx.RegisterType<IMvxJsonConverter, MvxJsonConverter>();

What’s the fix?

The above article says the fix is this:

Settings = new JsonSerializerSettings
{
    PreserveReferencesHandling = PreserveReferencesHandling.Objects

(it does go into more detail and I encourage everyone to read it).

So this is a change to MVVM Cross

Might be.

However, you can always use your own Serializer. One of the things that I’ve come to really like about MVVM Cross if that if you don’t like something, just write your own and override that specific thing. I stole this code, verbatim, from MVVM Cross, with the single exception of the one line above:

    public class MyJSonConverter : IMvxJsonConverter
    {
        private static readonly JsonSerializerSettings Settings;

        static KMJSonConverter()
        {
            Settings = new JsonSerializerSettings
            {
                ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize,
                Converters = new List<JsonConverter>
                        {
                            new MvxEnumJsonConverter(),
                        },
                DateFormatHandling = DateFormatHandling.IsoDateFormat,
                PreserveReferencesHandling = PreserveReferencesHandling.Objects
            };
        }

        public T DeserializeObject<T>(string inputText)
        {
            return JsonConvert.DeserializeObject<T>(inputText, Settings);
        }

        public string SerializeObject(object toSerialise)
        {
            return JsonConvert.SerializeObject(toSerialise, Formatting.None, Settings);
        }

        public object DeserializeObject(Type type, string inputText)
        {
            return JsonConvert.DeserializeObject(inputText, type, Settings);
        }
    }

Now, in App.cs, just change how it is registered:

Mvx.RegisterType<IMvxJsonConverter, MyJSonConverter>();
//Mvx.RegisterType<IMvxJsonConverter, MvxJsonConverter>();

Job done. It now works!

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.