OS X games from the ground up: command line arguments and decisions

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: functions and random numbers.

In the previous post you learned how to generate random numbers and how to use these to display a randomly generated string. I also left you with a couple of tricky challenges.

The first was to think of a situation in which you might want your random numbers to always start at the same point in the sequence by using srand with an integer literal rather than with the time function. The answer is when you’re testing your code. If you are trying to remove errors from your code, it’s often useful to be able to test it several times using exactly the same conditions. This way you can be sure that the changes you’ve made have actually fixed the problem, and not that the problem has just been temporarily masked because some values have changed.

The second challenge was to think about why our method of getting random numbers in a particular range will often be biased towards one part of the range. Here’s why. On my machine, RAND_MAX is set to 2,147,483,647. If we divide this by 13 we get 165,191,049.76923076923077. In other words, RAND_MAX is not exactly divisible by the size of our range. The highest multiple of 13 that is less than or equal to RAND_MAX is 2,147,483,637, which leaves a remainder of 10. This means that there is a very tiny bias in favour of numbers 0 to 9 in our range, because those numbers have a 1 in 165,191,050th greater chance of appearing. The larger your range gets, the more bias there is likely to be towards one part of it. For example, if you were choosing random numbers from a range of 100,000, the numbers 0 to 83,646 would have a 1 in 21,475th greater chance of appearing. If you were developing a scientific application that required a high degree of accuracy, you might want to find a solution to this. For the purposes of a game however, and with a range of this magnitude, this bias is so small as to make no appreciable difference.

In the previous post you also learned about functions. So far you’ve called various functions from your programme, but you haven’t seen what a function looks like. Well actually you have. All the code you’ve written so far has been within a special function called main.  It’s special because it is the only function that is required in every single C programme. When a C programme starts it always starts by calling the main function. Let’s have a look at your programme again to see how it’s structured:

The main function begins on line 13. You’ll recall from the previous post that some functions return a value and the value must always be of one type. When we define a function, we start with a keyword to show what type it returns, in the same way as we start variable declarations with a type. You can see from line 13 that the main function returns an integer.

The next part of the function definition is the name of the function, in this case main.

You’ll also recall that, for some functions, we included comma-separated arguments within parentheses. When you define a function you must specify what arguments you expect and what their types are. You can see that the main function accepts two arguments: an integer called argc, and string array called argv. Note that the string array also has a const keyword. You may remember from a previous post that this means the contents of the array may not be changed. If we try to change them from within the function, we’ll get an error. If a function has no arguments, you will still include the parentheses, but they will have nothing within them.

Next we have the body of the function – this is the code that the function executes when it is called. The body of the function is included within a pair of braces. You can see these in lines 14 and 27.

Line 16 of the function is a comment. Comments begin with two forward slashes. This tells C, “everything on this line from this point on is a comment for my benefit, so please ignore it.” Comments are a useful way of reminding yourself, or other people who view your code, what each part of it does, so you should use them liberally. Although this comment is within your function, you can use them anywhere in your file. You can see other examples of comments in lines 1 to 7. The comment on line 16 was placed there by Xcode to show you where to put your own code. You can delete this now.

Lines 17 to 24 are the code that you added in the earlier parts of this tutorial.

Line 26 was also created by Xcode. It’s a return statement. A return statement ends the function and returns control to the code that called it. When a function returns a value, the return statement should end with the value to be returned to the calling code. In this case we are returning the int 0. Zero, in this context, is a way of this function telling the calling code, I’ve finished running and everything is OK.

If you define a function that doesn’t return a value, then a return statement is optional, because your function will also return when it reaches the closing brace, like the one on line 27.

You may remember, when you chose the template for this programme in Xcode, that you selected a Command Line Tool. What is this? Since you own a Mac, you are probably most familiar with programmes that run in a graphical user interface (or GUI). These are programmes that use windows, buttons, text boxes and so on. However, your Mac can also write programmes that run from a command line. To explore what that means we have to venture outside of Xcode for a bit.

First though, you’ll need to find out where Xcode has stored the built version of your programme. First build and run your programme to make sure it is working correctly. Now go to the Project Navigator, if you are not already there, and open the group called Products. You should see a single item within it, called Buzzword. This is your built programme, and this is what Xcode has been rebuilding and running every time you ask it to:

Buzzword executable in Xcode

 

To find out where this built programme has been stored, first click it to select it. Now look at the narrow pane on the far right of the window. This is the Utilities pane.

Utilities paneIf you don’t see this pane, look for the following icon at the top right of the Window:

Xcode utilities icon

If it’s grey, not blue, click it and the Utilities pane will appear.

At the top of the Utilities pane, click the icon on the left that looks like a document page with the top-right corner turned down. This shows the File Inspector:

Xcode file inspector

In the File Inspector, Full Path shows where the built programme is located on your hard drive. The path on your machine will be different to the one shown here. Click and drag to select the path – make sure you get all of it. Then, while it is highlighted in blue, right click it and select Copy.

To run this programme outside of Xcode, we need to use an application called Terminal. The easiest way to find this is type it into Spotlight Search (click the little magnifying glass icon in the top right of your screen, type Terminal, and select it from the list). You should then see a window that looks similar to this, except the information at the top will be different for you:

Terminal

Now click on the hollow square in this window so that it goes solid, and type cd “ (that’s the letters c and d followed by a space then an opening quote mark). Now right click at the end of what you’ve typed and select Paste. The path you copied earlier should be pasted in. Now use the backspace key on your keyboard to carefully delete the word Buzzword from the end of the path. Don’t delete the forward slash that comes just before it. Now type  (that’s a closing quote mark). All being well, you should have something like this:

Terminal cd command

What you have constructed here is a cd command, which is short for change directory (directory is another name for a folder). Press the enter key on your keyboard, and you should see something like this:

Terminal after cd command

What you have done is made the folder (or directory) containing your built programme be your current folder. Now you are at the correct location, you can run your programme. Enter ./Buzzword so that your Terminal window looks like this:

Terminal Buzzword command

Although the programme is just called Buzzword, the ./ at the beginning is a way of telling Terminal, the programme I want to run is located in the current folder. Now hit return and you should see something like:Running Buzzword in Terminal:

Congratulations, you’ve just run your first programme outside of Xcode. We’re going to go back to Xcode for a little bit now, but keep the Terminal window open as we will be coming back to it later.

Back in Xcode, select your main.c file in the Project Navigator. Now you already know that some functions take arguments, which are included in the parentheses after the function name and separated by commas if there’s more than one. You’ve already seen examples of this when you’ve used the printf, srand and time functions. You can also see that the main function in your programme accepts two arguments, argc and argv. What you might be wondering is, if main is the first function to be called in every C programme, where do those arguments come from? The answer is, they come from the user.

When you typed Buzzword into terminal to run your programme, you could have also followed it with one or more arguments separated by spaces, e.g.

./Buzzword argument1 argument2 argument3

What C does when it finds these arguments is count them. It then puts that number into argc (which you can probably now guess stands for argument count). Then it takes each argument and puts them, in order, into the elements of the array argv (which stands for argument vector). When your programme starts, it can then access the argument count and argument vector and use them as it needs to. This is one way of getting user input into a command line programme.

To see how this works, we’ll modify our programme so that it accepts a single argument. This will be an integer representing the number of phrases that we want displayed.

The first thing to do with any kind of user input to your programme is to validate it. Otherwise users who are not sure what they are doing (or who are just plain malicious), could break your programme with nonsensical input.

The first bit of validation we will do is to check that we have received the right number of arguments.  Change your programme so it looks like this:

The new code is in lines 15 to 18, and there’s a change to line 29. The if command in line 15 is a very important part of the C language. Up to now our programme has executed each statement in order, starting from the top of the main function and working towards the bottom. Sometimes though we might want to execute some code only under certain conditions. This is where the if command comes in. The if command will evaluate whether an expression is true or false and then only execute some code if the expression is true.

The expression to be evaluated is placed in parentheses after the if command. This is known as the condition. So in this case, the expression we are evaluating is argc != 2. Notice that we use the argument name in exactly the same way we would use a normal variable. This is the first time you have encountered the != operator. It means not equal. In other words, we are checking to see if argc is not equal to 2. So this expression will evaluate as true if argc is equal to any other value than 2 and it will evaluate as false only if argc is equal to 2.

Now at this point you might be thinking, “hold on, I thought we wanted just one argument – the number of phrases that the user wants displayed – so why are we looking for two?”. Well, when C collects all the arguments, it also includes whatever you typed to start the programme. So every C programme will always have at least one argument, which in our case is “./Buzzword”. Any additional arguments will start from the second element of argv. This is why we are looking for a count of 2 and not 1.

OK what happens if the condition is true? In this case C will execute the body of the if statement, which is all of the statements within the braces that follow the if command. In our case, this will be the statements on lines 16 and 17. Note that the closing brace is not followed by a semi-colon, but the statements within the braces are.

If the user has entered the wrong number of arguments, our programme will use a printf function to display a useful reminder of how it should be used. This is followed by a return statement. This immediately ends the programme. Notice that we have returned the value EXIT_FAILURE and the return value at the end of the function has been changed from 0 to EXIT_SUCCESS. You’ll recall that returning a code of 0 indicates that the programme ran successfully and terminated normally. In this case, because the programme has detected the wrong number of arguments and is ending abnormally, we return a non-zero value. C has two special values defined, called EXIT_FAILURE and EXIT_SUCCESS. When the code is built, these will be replaced with values that are appropriate for the system the code is being built for. Using them helps to make our code more portable between systems and to future proof it to some extent.

If the condition is not true then the body of the if, i.e. all of the code between the braces (lines 16 and 17), will be ignored and execution will continue from the first statement following the closing brace (i.e. line 20).

Let’s see how this works. Rebuild the programme by selecting the Product menu and then Build. This is how you build a programme if you don’t also want to run it immediately after the build. You can also use the short cut – hold down the cmd key and press B. Now return to the Terminal window. If you’ve closed it, just repeat the instructions from earlier in this post to reopen it and make the build folder be the current folder.

Now try entering ./Buzzword without any arguments. This time you should see the following helpful message:

Terminal with incorrect number of arguments

Now try it again, but with two arguments, e.g. ./Buzzword 1 2 (note that arguments on the command line are separated with spaces, not commas and you don’t put parentheses around them like you would in a function call). You will get the same message.

Finally try it with just one argument, e.g. ./Buzzword 1 and this time you will be rewarded with a random phrase, as before. Note that all we are doing at the moment is checking for the right number of arguments, we aren’t doing anything with the argument we receive. In fact it doesn’t matter at the moment whether your argument is 1, 99, or bananas, your programme will quite happily give you a phrase in response to any of them, but it will only give one phrase, regardless of what number you use.

We’ll fix that in a minute, but for now let’s fix something else that’s a bit annoying – having to go out to Terminal every time you want to test your programme with command line arguments. Well Xcode has a way round that too. You can close the terminal window now and return to Xcode.

Normally Xcode will run your programme without any arguments. Try it now by hitting the run button and you’ll see the now familiar complaint from your programme when it doesn’t receive the right number of arguments. Let’s fix that now. Go to the Product menu and select Scheme then Edit Scheme…. You should see a window that looks like this:

xcode edit scheme

If it looks a little different, check that Run Buzzword is selected on the left hand side.

Each time you build in Xcode, you build for a specific target or targets. A target is a version of the product intended for a particular system or range of systems. Because you selected a particular template when you started this project, Xcode created a default target for you.

Xcode also has the concept of schemes. A scheme specifies the target or targets to build for,  a build configuration and a set of tests to run. You haven’t yet looked at how to make changes to targets or configurations or how to set up tests. You’ll look at all of these in a future post. Xcode also creates a default scheme for you. You’re going to edit that scheme now to supply a command line argument when you run the application.

Click the + button at the bottom of the section marked Arguments Passed on Launch.  In the text box that appears, type the number 1. The Edit Scheme window should now look like this:

Xcode add command line argument

Click OK. Now try building and running again. This time the programme will run successfully and show you a random phrase again.

Now that we are able to supply an argument from within Xcode, let’s turn our attention back to verifying the user’s input. So far we have checked that the programme received exactly one argument, but at the moment that argument can be anything. We need to make sure it’s a number.

Change your programme to look this this:

The new/changed code is on lines 12 and 16 to 31. When you enter this code, Xcode will show you two symbols in the margin, like this:

Xcode errors

Ignore these for now – we will revisit them later.

The first thing to notice is that our if statement has now become a bit more involved. You’ll recall that the body of an if statement is only executed if the condition is true. In our case, the code is only executed if argc does not equal 2. But we can also include some code that will only be executed if the condition is false. To do this, we add the keyword else after our first set of braces, then we add a second set of braces with the code that is to be executed if the condition is false. In our case, if the condition is false, i.e. argc does equal 2, then the code in lines 23 to 28 will be executed. This is shown in the diagram:

if else decision

You’ll recall from the earlier discussion that the second argument to main is an array of string pointers and that each element points to one of the arguments supplied on the command line. We can obtain the string representing any of the arguments by specifying the relevant array subscript. Note, in line 23, that we get the second element of the array, [1], and remember that this is because the first element, [0], points to the text entered to start the programme.

Because all the command line arguments are stored as strings, we need to convert the argument to an integer before we can do anything useful with it. We can convert a string to an integer using the strtol function. This is short for ‘string to long’. Long is another type of integer that supports larger numbers than a standard int. The maximum number an integer variable can hold depends on how many bits (binary digits) it has. The size of these variables depends on which system your code is compiled to run on, but generally a long has more bits than an int. For example, an int might have 32 bits, which would mean it could store numbers between -2,147,483,648 and 2,147,483,647, while a long might have 64 bits, which would mean it could store numbers between -9,223,372,036,854,775,808 and 9,223,372,036,854,775,807.

Because we want an int rather than a long, we also use a typecast, (int), to treat the result as if it were an int. You might be wondering why we don’t just use a typecast directly on the string to convert it to an integer. The reason is because type casting does not actually change a value, it simply asks C to reinterpret it as another type.. When we use a typecast to view a long as if it were an int we know that c is always going to be able to complete the operation because it is treating one number as another type of number. The worst thing that will happen is that some information will be lost  because the new int might not be big enough to hold the old number, but the programme won’t crash. Remember that a string array doesn’t hold the actual text, it holds pointers to the text in memory, and a pointer is a large number. So if we try to us a typecast on a string pointer, C assumes we simply want to treat the pointer as if it was a int – so we’ll end up with some sort of number, but not the one that we intended to get.

That’s where the strtol function comes in. It will try to make sense of the string as a number. If it can it will give you the number. If it can’t, it won’t crash, it will simply let you know – “Hey, I tried to make this happen, but it just didn’t work out. OK?” How does it do this? The function strtol is designed to work with a special error handling library called errno (short for error number). You imported this library on line 12. This library creates a special variable, also called errno. When strtol is called, it changes the value in errno depending on how successful the conversion was. If the string could not be converted, errno will contain a value defined as EINVAL. So on line 25 we can check to see if errno is equal to this value. If it is, we know that the user has entered a non-sensensical argument and we can end the programme and show them the usage message.

You might be wondering about the second and third parameters for strtol. The second parameter is an optional string pointer. If this parameter is present and an error occurs, strtol will set this pointer to point to the first invalid character in the string supplied as the first parameter. This is useful if you need to do more advanced error handling, but is not required for our purposes, so we set it to NULL. The third parameter indicates what base the number should be interpreted as. In computing we often use a number base other than 10, most predominantly base 2 (binary), base 8 (octal) and base 16 (hexadecimal). We won’t torture our users by requiring them to use anything other than decimal, so we’ve set the base to 10.

Notice that when we checked for EINVAL on line 25, the equality operator we use is not a single = sign, but a double ==. A single = sign means, I want whatever is on the left of the operator to be equal to what is on the right of the operator. If you used a single = sign here, so that the condition was if(errno = EINVAL), what do you think would happen?

C would interpret the bit in the brackets as meaning, “I want errno to be set equal to EINVAL”. This is exactly what it would do. Not only that, but an assignment always evaluates as the value of the assignment. C treats zero as being equivalent to false and any non-zero number as being equivalent to true, so whether the code between the first set of braces would be executed, would depend on the value of EINVAL. You can see how easy it is to break your programme by using = when you meant to use ==. Believe me when I say you will do this a lot while you are learning, and you will continue to do it a lot, even when you are experienced, so it’s one of the first things to check for when your programme doesn’t work as you expect.

Fortunately for us, Xcode is very helpful with things like this. In line 25, change the == (equality) operator to an = (assignment) operator and see what happens. Xcode will add another one of those funny symbols to the margin:

xcode equality vs assignment error

What is this? Whenever you see a yellow triangle in the margin, it’s Xcode warning you that you might be trying to do something that could result in your programme not working as you expect. Note the use of the words ‘might’ and ‘could’ in that sentence. A warning doesn’t necessarily mean your programme has errors. It’s just Xcode telling you to be wary and double check your code. By default, Xcode will still happily compile and run your code, even if it contains warnings.

You might also see red hexagons in the margin, like this:

xcode error

This indicates an error. This symbol means, “there’s something wrong with this code – you need to fix it”. Unlike warnings, errors will stop Xcode from building your code. If you try to build a programme with errors in it, you’ll get a “Build Failed!” message.

So how do you know what the warning or error is? Go back to line 25 and click once on the warning symbol in the margin. You should now see that Xcode has placed a helpful message next to the line:

Xcode warning message

Xcode is helpfully telling us, “Hey, you’ve used an assignment operator in a condition – are you sure that’s what you want buddy?”. Clicking on the warning or error symbol is a quick way of checking what a particular issue is, but what if you had a lot of errors and warnings and you wanted to work through fixing them one by one? It’s time to introduce you to another navigator – the Issue Navigator. In the Navigator pane on the left side of the window, click the icon at the top that looks like the warning symbol – an exclamation point in a triangle. Your Navigator pane should now look like this:

Xcode issue navigator

This lists all the warnings and errors in your programme, sorted by file. You can see that the top warning is the one about our use of an assignment operator in a condition. Click once on this. Xcode will helpfully take you to the right place in the file and will also pop up a menu with some suggested actions to address the warning:

xcode warning actions

The first of these, “Place parentheses around the assignment to silence this warning” would be something we’d do if it had been our intention to use an assignment operator here. In this case, we want to use the second suggestion, “Use ‘==’ to turn this assignment into an equality comparison”. Click this option once now. Xcode will preview the change for you – you can still change your mind at this point and select another option. In this case though, we’re happy that this is the change we want. Double-click the option and Xcode will make the change and the warning will disappear. Hopefully by now, you’re starting to appreciate some of the additional benefits that working in an IDE like Xcode gives you over a simple text editor.

While we’re busy fixing warnings and errors, let’s turn our attention back to line 23. In this statement we are using the strtol function to convert our string into a number and we’re then assigning our number to an int variable called numberOfPhrases. If you look in the Issue Navigator, you can see that Xcode is warning us that the variable numberOfPhrases is unused. Well that’s strange, because if you look down at line 31, you can see we have used numberOfPhrases as an argument to a printf function. To complicate matters further, there’s an error on that line too. If you look in the Issue Navigator, you can see that the error is that we’ve used an undeclared identifier called numberOfPhrases. But we have declared it – back on line 23! What’s going on? Has Xcode lost the plot?

This is another issue you will frequently encounter as you are developing your programming skills. The problem is one of variable scope. Scope is an important concept in c. Imagine a theme park called “Fun Days” that gives you a choice for how you buy your tickets. You can go into the theme park and you can buy a ticket for an individual ride. If you just want to go on the Big Dipper, you can buy a ticket for that ride, but the ticket will only be valid for that ride. If you try to use that ticket to go on the the Crazy Swings ride, the attendant will say “Sorry – this ticket isn’t valid here.”

variable scope 1

However, I can choose instead to buy a “Fun Days” ticket at the gate that will let me into any ride within the park. Now the attendants at both the Big Dipper and the Crazy Swings rides will quite happily accept that ticket.

variable scope 2

You could say that the ‘scope’ of the Big Dipper ticket is limited to the the Big Dipper ride, while the ‘scope’ of the Fun Days ticket encompasses the whole theme park, which includes both the Big Dipper and Crazy Swings rides.

In C, whenever you use braces, {}, the code within the braces becomes an entity in its own right. So consider your main function as being somewhat like the Fun Days theme park, and consider the code within the braces following your else command as being like the Big Dipper ride. When you declare a variable inside the braces following your else command, that’s like buying a ticket that’s just for the Big Dipper ride. While you’re within those braces it’s valid, but try to use it outside of the braces and it won’t be valid.variable scope 3

So how do we get round this? Well, ideally we want a ticket/variable that’s valid for the Big Dipper ride/else braces but can also be used on the Crazy Swings/printf function outside the braces. The solution for the rides is to trade up to a Fun Days ticket which you buy at the gate. We’re going to adopt the same solution for the variable. By declaring it within the main body of our function, it will be available throughout the function, not just within the else braces.

On line 16 you’ll see I’ve included a declaration of the numberOfPhrases variable, but I’ve put two forward slashes at the start of the line. You’ll recall from earlier in this post that this turns the line into a comment. Previously we’ve used comments to simply add useful information to the file. Another common use of comments is to temporarily disable lines of code. In a later post we’ll explore why this is useful in testing. For now, delete the backslashes so the statement is enabled. You should now see that the error on line 31 has gone. but the error on line 23 is still there. Why is this?

Another aspect of scope is that you are allowed to redefine a variable, as long as the scope for the redefined variable is different from the scope of the original variable. When you do this, the new version of the variable hides the previous version while the new version is within scope. The following example shows how this works:

hidden variables

So that’s what’s happening with our code. Because we still have the variable declaration in line 23, we are effectively defining a new version of numberOfPhrases that will only be visible within the else braces. This is not what we want, so fix it by changing line 23 to read:

This should make the final warning disappear. Don’t worry if you’re still struggling to get your head around the concept of scope. It’s one of the trickier concepts in programming and it will come with practice as you analyse more code.

Now let’s check if our new validation code is working. Follow the earlier instructions to edit the scheme. Double click the argument to edit it and change it to something like “bananas”. Click OK then build and run the programme and you should see the usage notice.

Go back and edit the scheme again, and change the argument to an integer number, e.g. 99. Now when you click OK then build and run, you should see something like this:

Buzzword successfully read command line number

Great work. In the next post you’ll continue to make improvements to your programme. In the meantime, here’s a couple of challenges for you. It would be useful if the argument was optional and defaulted to a value of 1 if it was missing. How would you modify the code to allow for that possibility? While you’re at it, you might also want to set a sensible and reasonable lower and upper limit, say 1 to 99, on the number of phrases that can be generated. Here’s a hint. There are a few comparison operators we haven’t used yet that you might find useful for this exercise. They are: <, <=, > and >=. You should be able to work out what they do just by looking at them. Experiment, and we’ll explore them further in the next post.

For your second challenge, you might have noticed that we now have two bits of code, lines 19-20 and lines 26-27 that do the exact same thing, but at different places in your programme. Is this an acceptable situation? If you don’t think it’s acceptable, what might we be able to do about it? Solutions will be shown in the next post.

 

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

Leave a reply