OS X games from the ground up: user input

You can see an index of all the posts in this series: go to index.

If you are starting from this point, or need a fresh set of files, here are the starter files from the end of the previous post: loops.

In the previous post you used two types of loop, a while loop and a for loop, to display the number of phrases requested by the user. I also left you with the challenge of using a do … while loop to achieve the same thing. Here’s my solution:

The changed code is on lines 50 to 55. Your solution may be slightly different to this, but as long as it creates the same output as the previous versions of the loop, that’s fine.

The main thing to watch out for with do … while loops is that the body of the loop will always be executed at least once, regardless of the condition. For example, if, for some reason, numberOfPhrases was 0 when this loop was first reached,  the body of the loop would be executed once, printing a single phrase. Only at that point would the condition be checked and no further iterations of the loop would occur. Contrast this to the while loop, where the condition would have been checked as soon as the loop was reached, and the body of the loop would never have been executed.

The other thing to watch out for with do … while loops is that you remember to put the semicolon after the closing parenthesis for the condition.

OK after all that hard work with command line arguments, we are now going to dispense with them. Instead of the user entering the number of phrases on the command line, we are now going to replicate the behaviour of the original BASIC programme and get the user to press a key at the end of each phrase. If they enter Y they will get another phrase. If they enter anything else the programme will end.

Let’s have a quick look at the BASIC programme to see how this is achieved there.

Lines 110 to 130 should be familiar to you, as you examined them in an earlier post. These are the lines that generate and display a random phrase.

Line 150 has two statements. The first statement, INPUT Y$, displays a question mark, then waits for the user to type in a response and hit enter. The response is then saved in the specified string variable (Y$).

The next statement on this line should look a little familiar to you as it is BASIC’s version of an IF command. The condition is Y$=”Y”, in other words it checks to see if the user has entered the single letter Y. Notice that BASIC uses a single = sign for both assignment and equivalence operations. It can distinguish between the two based on context.

The part of the IF statement following the keyword THEN is equivalent to what’s in the braces in the C if statement. In this case it’s just a single number, 110. In BASIC, when a single number is used in this context, it means continue execution from this line. So, if the condition is true (i.e. the user has entered Y), control will return to the lines that display a random phrase.

Like C, BASIC also has an ELSE command for indicating code that should be run if the condition is not true. The code that follows this is the equivalent of the code that would be placed within the braces following the C else command. The statement to be executed here is GOTO 999. As you might have already guessed, this causes execution to continue from line 999.

The first statement in line 999 displays a final message to the user. In the second statement, END is BASIC’s equivalent of C’s exit function – it cause programme execution to end.

Make the following changes to the programme:

Quite a lot’s changed here. We no longer import the errno library, which used to come immediately after line 11. We’ve also lost the function declaration that used to come just after that. We’ve lost all of the code to get the command line argument and validate it, which used to be between lines 14 and 15. There’s new or changed code from lines 26 to 38. And we’ve lost the function that used to come after line 41.

As we’re no longer going to be reading arguments on the command line you can, if you wish, edit the scheme, select the argument you set up previously and click the button to remove it, then click OK.

Now let’s have a look at what’s happening in the new code. On line 26 we set up a char variable called input. This will be used to hold the character that is entered by the user. We’ve still got the line that displays a random phrase, on line 30 and it’s still within a do … while loop, from line 29 to line 36. Instead of checking a counter, as it did before, this condition checks to see whether input holds the letter ‘y’, which indicates that the user wants to see another phrase. If it doesn’t hold ‘y’, we display a farewell message on line 38 before ending the programme on line 40. This should all be familiar to you. But how do we get the letter the user enters and put it into input?

That’s taken care of by the scanf function in line 33, which is short for scan formatted and is part of the standard input-output library, stdio. If you think scanf looks mighty similar to printf, you’d be right. While printf sends data to the standard output, scanf reads data from the standard input, which in this case is the keyboard. Like printf, scanf takes a format string with conversion specifiers, and then one further parameter to match each conversion specifier. Our string is very simple, it’s just “%c”, which means read a single character into the variable I give you. This is followed by the name of the variable, input.

But what’s that & symbol doing in front of the variable name? When you use a variable name on its own, as you do in a printf command, C interprets this as meaning you want the value of the variable. But scanf doesn’t care what the current value of the variable is, it wants to put a new value there – the one that it reads from the standard input. So instead of using the variable name on its own, we prefix it with the reference operator, &. When C sees a variable name prefixed with &, instead of providing the value of the variable, it provides the variable’s address in memory. The scanf function then stores the value it reads at this address, which is equivalent to setting the variable to that value.

Let’s try it out. I’ve added a temporary printf command on line 34 so you can see what character has been captured by scanf. Before you run the programme, move your mouse over the top edge of debug pane until it turns into a double headed arrow. Now click and drag upwards to enlarge the pane. Now look for this icon at the bottom right of the debug pane:

Xcode Variables View toggle

If it’s blue not grey, click it once. This hides the variables view, which is a part of the debug pane (the left side) that you’ve not explored yet.

We’ve done all this to give a bit more room to the console. This is the right side of the pane where your programme output appears.

Now try building and running the programme. You should see the instructions, your first phrase and then a prompt: ?. The programme has now paused, waiting for you to enter some characters. First though, as we’re running within Xcode, you need to make sure the console has focus. Click once to the right of the ? in the console. You should now see a flashing cursor. If the programme is ever waiting for input and you can’t see a flashing cursor, just click once in the console to give it focus.

Buzzword input 1

OK let’s try a negative response first. Type n and then hit the enter key. You should see the programme display the farewell phrase and then end.

Buzzword input 2

Great! That’s exactly what we were expecting. Try running again, and this time let’s try a positive response. Type y and hit enter. You’ll see something like this:

Buzzword input 3

Woah! What happened there? The programme displayed another phrase as it should, but then, instead of stopping and waiting for your input again, it flaked out and ended. To understand why this was, we need to look a bit closer at what scanf does. When you type, the console in Xcode collects the characters until you press enter. Then it sends all the characters it has gathered, including the enter (which is interpreted as a newline, \n) to standard input or stdin as it’s referred to in c.

At this point your scanf function starts processing it. The scanf function takes characters from stdin and tries to interpret them based on the conversion specifiers we’ve given it. In this case we’ve given it just one conversion specifier, which is %c for a character. The first character it encounters is the y we typed. So scanf dutifully copies the y character into our variable called input and let’s the rest of your programme go about its business. So we display the entered character on line 34, then on line 36 we check to see if it is equal to ‘y’, which it is, and we return to the top of the loop on line 29. In the loop on line 30 we display another random phrase, then on line 32 we display another prompt.

At this point you might expect that the scanf function on line 33 would hang around and wait for you to enter something else. But it doesn’t. Why? Because when it looks at stdin, it finds that stdin is not empty – it still has the \n character we entered last time when we hit the enter key. So it dutifully copies the \n character to our variable, input, and control passes to the next line. This time when we get to our loop condition, input contains \n, which is not equal to ‘y’, so our programme ends.

This is not what we want at all. We want our programme to ignore the \n characters and just process the letters we type. Well there is a solution. Change line 33 so that there is a single space between the opening quote and %c, like this:

What this does is tell scanf, “look, I’m just not interested in any white space that comes before other characters. So if you find any spaces, tabs, newlines or other characters of that ilk, just throw them away. Thanks.”

Build and run your programme again. This time, when you enter y, you’ll get another phrase and the programme will wait for you to enter another character.

Hurrah, so we’ve solved the problem right? Well, we’ve solved a problem. But all is still not well in scanf land. To see what I mean, try running the programme again and enter yes instead of y. You should see something like this:

Buzzword input 4

Do you see what happened here? The first time through, scanf reads the y into input, but leaves the e and the s in stdin. The next time through, scanf sees that stdin isn’t empty and reads the e into input. Because e does not equal ‘y’, the programme ends.

So what can we do about this? Well the fundamental challenge with scanf is that it’s a messy eater – it takes what it wants from stdin and leaves the half-chewed remains on the plate. What we really need is a way to get everything from stdin, so we start with it empty each time. Change your code to this:

The changes are from line 26 to 35.

In line 26 we’ve added another variable, a character array called rawInput, with 80 elements. This is where we will store everything the user types.

Line 34 introduces a new function, fgets, which is part of the stdio library. The fgets function reads a stream of characters from a file stream and stores them in a character array. You haven’t yet been introduced to the concept of a file stream, but for now, all you need to know is that stdin is a type of file stream.

The fgets function takes three parameters. The first is the character array where fgets should store the characters it reads. We’ve given it the name of the array we set up in line 26, rawInput.

The second is the maximum number of characters that fgets should read. It needs to know this, because otherwise it could potentially write more characters than there is space for in the array. Here we’ve used sizeof(rawInput) to get that number. Although sizeof() looks like a function, it’s actually an operator. It returns the size of its operand in bytes. So here it will return 80, which is the size of the character array rawInput. You might be wondering why I didn’t just type 80 – after all we have only to look at line 26 to see that’s what size the array is. The reason for not doing so, is that at a later point I might decided to change the size of this array. If I do that, I have to remember to change the number, not just in the initialiser in line 26, but also here in the fgets function call. This is bad practice, because it’s too easy to forget to change both of them. By using sizeof, I know that the value returned is always going to be correct, no matter how often I change the size of the array in line 26.

The third parameter is the file stream to read from. In our case we want to read from stdin.

This function will now read the characters you type up to and including enter (\n) or the first 80 characters you type, whichever comes sooner. So what would happen to any characters over 80? Well, they’d stay in stdin and be read next time round. Note that this does mean a really determined user could still cause the programme to terminate by typing a very long phrase over 80 characters, but this will be more than enough to look after the users who simply type yes or yep or yeah or yea instead of y. Ok so we’ve now got the characters the user has entered, but how do we determine if the first of these characters is y?

You’ll notice on line 35 that we have something that looks remarkably similar to our scanf function. Whereas scanf reads from stdin, sscanf, which is also part of stdio, reads from a string – that’s what the extra s at the beginning stands for. It takes at least three parameters. The first is the string to be processed. We’ve given it the name of our character array, rawInput here. The remaining parameters are the same as the parameters of scanf – a string literal containing the conversion specifiers, and then, for each conversion specifier, the address of a variable where the output is to be stored.

You might be wondering why I’ve even bothered to use sscanf. Why not just get the first character using rawInput[0]? The answer is white space once again. We can’t guarantee that the user hasn’t put spaces or tabs before typing y. We can use the same trick with sscanf, as we did with scanf – adding a single space before the conversion specifier %c to force sscanf to ignore any white space.

OK try building and running your programme again. You should find that entering y, yes or yellow-bellied son of a gun all result in you getting another phrase for your money.

Since our input routine now works as we need it to, you can now remove line 36, which shows the character entered. Are we done? Almost. There’s just one more nicety we have to take care of.  If you try running your programme and entering Y (an upper case y) – it’ll dump you straight out on your ass. We need it to serve up another phrase regardless of whether the y is upper or lower case. You should be able to make a simple modification to line 37 to take care of this. See if you can work this out for yourself, then click below to see the solution.

Congratulations. You’ve completed your first programme in C. Here are the final files: user input. And here is a built copy of the final programme: Buzzword

You’ve now got enough tools in your belt to be able to write simple command line games. To prove it, in the next post I’ll give you a challenge to do exactly that.

This entry was posted in Games, Programming Tutorials and tagged , , , , , . Bookmark the permalink.

One Response to "OS X games from the ground up: user input"

Leave a reply