OS X games from the ground up: more on decisions and functions

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: command line arguments and decisions

In the previous post in this series, you learned how to read and validate command line arguments. I also left you with a couple of challenges. The first of these was to change the validation code so that, if the programme is run with no arguments, it should default to showing one phrase.

Here’s my solution:

Your solution might not be exactly the same as mine, but as long as your programme behaves in the correct way, that’s fine.

Notice that, now our programme is starting to get a bit larger, I’ve added comments to explain what each section does. If you are intending to share your code with other programmers, or have other programmers collaborate with you on a project, commenting your code is essential. But, even if that isn’t the case, it’s still a good habit to get into. Although you might not need those comments now, when you come back to work on an old piece of code you haven’t looked at for a while, you’ll be glad you made the effort as it will save you a lot of head-scratching and frustration trying to work out why you did things in a certain way.

OK, let’s have a look at what’s going on here. In line 18 I’ve added a new if command that checks to see if argc is equal to 1. Remember that there is always at least one command line argument, which is the path and filename that was entered to run the programme. So if argc == 1 is true, that means the user didn’t enter any additional arguments. If this is the case we simply set the numberOfPhrases to the default value of 1 on line 20.

On line 21 you can see the else command that you are used to, but it’s immediately followed by another if command. The condition in the second if will only get checked if the condition for the first if is false. You can chain any number of if commands together this way:

chaining if statements

So you can see that the logic for our programme is:

  • Check if no arguments have been given (line 18) and use a default value if so (line 20).
  • If some arguments have been given, check that the right number of arguments have been given (line 21) and quit if not (lines 23 and 24).
  • If neither of those things is true, then that means exactly one argument has been given, so get it and attempt to convert it into an integer (lines 27 and 28), quitting if it can’t be converted (lines 32 and 33).

OK let’s check if all this works. First edit your scheme, as you learned in the previous post, so we can switch off the command line argument. Note that you don’t have to remove the command line argument, you can simply uncheck the check box next to it to disable it, then click OK:

Xcode disable command line arguments

Now try building and running your programme. It should detect there are no arguments and set the number of phrases to the default value of 1:

Buzzword default number of phrases for no argument

Now edit the scheme again, make sure that your argument is set to a number other than 1 and re-enable it, before clicking OK. Now build and run again and this time you should see a message confirming that you requested that number of phrases:

Buzzword successfully read command line number

The other part of this challenge was to check that the number of phrases requested fell within a reasonable range, e.g. 1 to 99. At the moment your programme will quite happily accept any integer number between -2,147,483,648 and 2,147,483,647. A negative number or 0 doesn’t make sense and a sufficiently large positive number could crash your programme.

Here’s my solution:

The changes are on lines 30 and 31. Notice that, rather than adding another else if to the chain, I’m hijacking the condition that checks if the number was converted successfully. I’ve introduced a couple of new operators here. The first two, < and >, return true if the left operand is less than the right operand, or if the left operand is greater than the right operand respectively. There are also two associated operators, <= and >=, that return true if the left operand is less than or equal to the right operand, or if the left operand is greater than or equal to the right operand respectively. So the two checks we are making here are to see if the numberOfPhrases is less than 1 or greater than 99.

So what are those funny double vertical lines, ||,  between the conditions? These are yet another operator, called the logical OR operator. There are two logical operators that can be used to combine the conditions in a statement:

  • The OR operator, ||, will return true if either the left operand or right operand is true (or if both operands are true) and false if the left operand and right operand are both false.
  • The AND operator, &&, will return true only if both the left operand and right operand are true, and will return false for all other cases.

There is also a third logical operator called NOT, !, that takes a single operand and will change it’s value from true to false, or from false to true.

It’s common to express logical operations like this in a truth table, which shows the output for all possible combinations of input. Here are the truth tables for these three operators:

a || b (a OR b)

a b output
false false false
false true true
true false true
true true true

a && b (a AND b)

a b output
false false false
false true false
true false false
true true true

!a (NOT a)

a output
false true
true false

An important thing to note about logical operations is that C will be efficient about the way it checks them. So in our statement on line 30, if it discovers that errno == EINVAL is true, it won’t bother to check the other two conditions since it knows that their results are no longer going to make any difference to the result of the whole expression. Can you see why?

An OR operation is true if either of the operands is true, so as soon as any of the conditions in this expression is true, the whole expression must be true. You’d get a similar situation if the expression was something like (errno == EINVAL && numberOfPhrases <1). In this situation, if errno==EINVAL turned out to be false, c wouldn’t bother checking to see if numberOfPhrases was less then 1 because the result is irrelevant at that point – in an AND operation, if either operand is false the whole expression must evaluate to false.

Again, if you attempted this part of the challenge, you probably came up with a different solution to me. As long as your solution results in the correct behaviour, that’s fine. Before we move on, test your programme with:

  • No arguments
  • Too many arguments
  • A single argument that’s a word rather than a number
  • A single argument that’s too small (0 or negative)
  • A single argument that’s too big (100 or higher)
  • A single argument that’s within the correct range (1 to 99)

Check that you get the results you expect for each of these tests. You’ll notice that I didn’t ask you to test just the new checks for range, but all of the previous validation checks too. This is a practice known as regression testing. It checks that changes you’ve made to your programme haven’t introduced new errors into code that was previously working correctly. It’s sensible to get into the habit of doing this whenever you make changes to your code.

I set you a second challenge at the end of the previous post, which was to consider that we have two blocks of code, lines 23 – 24 and lines 32 – 33 that are identical. I asked you to think about whether this was an acceptable situation. If you thought it was acceptable, consider this. We’ve now made some changes to our programme so it only accepts numbers within the range 1 to 99. We should change our usage message to reflect this. Currently though, our usage message is in two different places in our programme. If we change it in one of these places, we have to change it in the other place too. If we forget to make the change in one place, we’ve created a poor user experience. In more extreme cases, not updating duplicate sections of code correctly could introduce errors.

The solution to this is to avoid the situation where you have two or more sections of code that perform an identical function. So what can we do about this? One solution is to rewrite the code in our main function so that the usage message only has to appear in one place. Changing the structure of code so that it results in the same output but does it in a more efficient way is known as refactoring. It’s something we’ll revisit in future posts.

The other approach, and the one we’ll take, is to move the code that’s duplicated into its own function. We can then call that function whenever we need that code to run. (NB there is actually a third way round this problem, which is to use something called macros – this is an advanced topic that we’ll consider in a future post).

The new function will simply display a usage message and then end the programme. Change your programme as follows:

The new/changed lines are 14 and 15, 26, 34 and 56 to 60.

The new function starts at 56. You should give functions names that represent what they do, so a good name for this function is displayUsageAndExit. Note again that I am using camel case for my function names. You don’t have to do this, but whatever style you choose, you should be consistent with it. Note that, like variables, function names can contain only upper and lower case letters, digits and the underscore character. You are not permitted to begin a function name with a digit. Now this function doesn’t need any parameters, so the function name is followed by empty parentheses: displayUsageAndExit(). You’ll remember from our analysis of the main function that the function name is preceded with the return type. Our new function won’t return any value, and to indicate this we use the keyword void.

Again, remember that the body of the function is placed between a pair of braces. Some programmers like to put the opening brace on the same line as the function name, like this:

void displayUsageAndExit() {
   .
   .
   .
}

It doesn’t really matter which style you use, as long as you are consistent with it.

You are already familiar with the printf function on line 58, but note that I’ve now expanded the usage statement to mention the permitted range.

The exit function on line 59 is new. This function is part of the standard library, stdlib. The exit function ends the current programme. It accepts a single parameter, which is a status code it sends the parent process. In other words, exit(EXIT_FAILURE) used in this function is performing a similar role to return EXIT_FAILURE in the main function. So why didn’t we just use return EXIT_FAILURE in this function as well?

You’ll recall that every C programme has a main function and that main is the first function that is called when the programme begins. In other words, main is called by the parent process – which in our case has been either Xcode or Terminal, depending on how we’ve been running the programme. When we use a return statement in main, it returns control to the parent process, effectively ending the programme.

Our function, displayUsageAndExit, is not called by the parent process, it’s called by main, in lines 26 and 34. So if we were to use a return statement in displayUsageAndExit it would return control to main. We’d have displayed our usage message, but our programme would still be running – that’s not what we want at all. So, if you want to end the programme in a function other than main, you need to use the exit function instead.

Before we move on, you might have noticed line 15. This looks identical to the start of our function definition, but it ends with a semicolon instead of a function body. C expects every function to be declared before it is used (in a similar way to how we declare variables before we use them). To declare a function you write the function return type, name and arguments in parentheses, followed by a semicolon. The function declaration can be anywhere in your file, but it must appear before the first call to that function. Practically though, you should group all your function declarations together near the beginning of the file.

OK run your tests again, and you should find the programme behaves identically to the way it did before we created the new function.

In the next post, we’ll finally get round to doing something with that user input.

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

Leave a reply