OS X Games from the Ground Up: Building a User Interface with NCurses (1)

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

If you are starting from this point, you can download the starter files here: introducing_ncurses

In the previous post you learned how to link the NCurses and Panel libraries to the Hexapawn project and you were introduced to how windows and panels work in NCurses. In this part we’ll start using these features to build a text-based user interface for the game.

We could just build the windows we need manually, but since we will have quite a few different windows to build on this project, it makes sense to create some generalised functions to manage them. As this project will grow to be a lot bigger than either of the two previous games we’ve worked on, another thing we’ll do is start organising functional areas of the game into different files. Let’s start by creating a new file to hold our windows-related functions. With the project open in Xcode, open the File menu and select New > File…. In the dialogue window that appears, make sure OS X and Source are selected on the left, and then select the C File template on the right.

Hexapawn new file

Click Next. Name the file hexwindows and make sure that Also create a header file is ticked. We’ll learn what the header file is for a little later.

Hexapawn name new file

Click Next again. In the next dialogue window, just check that the proposed location for the file is the Hexapawn folder (you should see main.c already in there), and check that the file will be put into the Hexapawn Group and that Hexapawn is selected in the Targets section.

Hexapawn create file

 

If all looks well, click Create. You should now see two new files in the Project Explorer:

Hexapawn new files

Let’s move the NCurses initialising code into its own function. Open hexwindows.c and enter the following code:

You’ll notice I’ve taken the opportunity to add a couple more function calls to our initialisation code. Normally the characters you type as input are placed in a buffer and are not made available to the programme until the return key is pressed. Line 16, cbreak(), disables this behaviour so that characters are available to the programme immediately that they are typed. Line 18, keypad(stdscr, TRUE), causes special keyboard keys, such as function keys and arrow keys to be returned as a single value in the input stream. This will make it easier for us to implement our user interface because we won’t have to interpret the escape sequences that are normally generated by these keys.

OK, let’s try our new function out. Go back to main.c and replace  lines 12 to 15 with a call to our new function:

You’ll immediately get an error: Implicit declaration of function ‘initialise_curses’ is invalid in C99. What’s going on? Well the problem is the function is in a different C file, so main.c can’t see it. You’ll recall from the earlier parts of this series that we got around issues of function visibility by adding a function declaration to the top of the file. But which file should we add it to? We can’t add it to hexwindows.c because we’ve already established that main.c can’t see the contents of that file. We could add it to the top of main.c. That would work, but what would happen if we wanted to use that function in another C file that we create later? We’d have to add the declaration there as well.

The solution is to use something called a header file. A header file is a file that contains all the information, including function declarations, that you want to share with other parts of your programme. Let’s start the header file for hexwindows.c. Open hexwindows.h and add the following code:

 

Note that the lines beginning with # have been added for you automatically by Xcode. Don’t worry about what these are for now (we’ll come back to them in a future post), just be sure that any code you add to this file is placed after #define __Hexpawn__hexwindows__  and before  #endif /* defined(__Hexapawn__hexwindows__) */ .

Now go to main.c and add the following line, after line 7:

Note that when we are including header files that we have created, we use quotes rather than the angle brackets we’ve been using to include library header files. You should now see that the error has disappeared and the programme should build and run successfully.

What’s next? Well ideally we want to create another function that will create a game window for us. Let’s think about what that function should do:

  • It should accept the dimensions for the new window as parameters
  • It should create a new window to those dimensions
  • It should set the window up to accept special keys as input (e.g. function keys, arrow keys)
  • It should draw a border round the edge of the window
  • It should create a new panel and associate it with the window
  • It should return a pointer to the panel and the window

That all looks absolutely fine except for the last bit. How can we return more than one value from a single function? I’ll let you into a little secret here – there is a way of doing it, and we’ll find out what that is later on. But for now let’s explore a different solution.

You might recall from an earlier part of this tutorial series that we introduced the idea of indirection – referring to an object in memory by using using its address rather than its name. We used this when we wanted to modify an existing string in a function. We simply passed a pointer to the string to that function. The function was then able to access the string directly so it could modify it. So why don’t we take the same approach here? If we create a pointer to a window and a pointer to a panel outside of the function, we can simply pass the addresses of those pointers to the function so it can modify them directly.

Add a new function header to hexwindows.h that looks like this:

And add a new function to hexwindows.c that looks like this:

Woah! What’s the deal with those double asterisks? Weren’t we just going to pass through the addresses of the window and panel pointers? Well yes, but let’s think about what’s going on here. When we create a new window, e.g.

what gets returned to us is a pointer to the data for that window (in other words, the address of the data). If we were to pass that value directly to a function, e.g.:

what the function is getting is the address of the window data:

wrong_level_of_indirection

This is not what we want. We want our function to modify the pointer variable itself, not the data it points to. So what we need to pass to our function is the address of the pointer variable, in other words a pointer to the pointer variable!

right_level_of_indirection

 

Now that we have the address of the pointer variable in the function, how do we read or write the value inside it, for example, when we want to set it the address of some new window data we’ve just created? You would be forgiven for thinking we could just do this:

But this wouldn’t work, because this line is asking C to take a pointer to a pointer to some window data and turn it into an ordinary pointer to some window data:

wrong_level_of_indirection_2

 

But actually we want to modify the content of the pointer, and we can get to that content by using the asterisk operator in a slightly different way:

Which has this effect:

right_level_of_indirection_2

 

Don’t worry if you are struggling to follow this. As I’ve said before, pointers are the most difficult aspect of C to get to grips with, especially when you start dealing with multiple levels of indirection. Stick with it, and one day it will all just click into place. You will get plenty of practice during the rest of this series of tutorials. My advice is, if something doesn’t make sense, try drawing a diagram of what’s happening, like those above.

Let’s now go back to our new function and see what it’s doing. Line 27 is creating a new window and making our window pointer point to it. In line 29 we set the window up to accept special keys. In line 32 we create a new panel and associate it with the new window, we then make our panel pointer point to this panel. Finally in line 35 we draw a box around the window. Notice how we use the * operator everywhere we want to refer to the content of the pointer.

OK, so how do we use this new function? Go back to main.c and change the code that creates and displays the windows to this:

Note that we are using the address-of operator (&) to pass through the address of the window and panel pointers rather than their content. If you forget to include this operator, Xcode will complain, because it’s expecting a pointer to a pointer here.

In the next part of this series we’ll continue to build our interface by creating the first of our actual game windows. In the meantime, here’s a challenge for you. Like all the other main windows in the game, the game window, in which you play a game against the computer, will be 24 rows high by 80 characters wide and will be positioned at the top-left corner of the terminal window. It will have a border, and a title – “Hexapawn: Play a game”. You now know enough about NCurses to be able to build this window using the functions we have created so far. See if you can do it, and we’ll look at one possible solution in the next post.

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

2 Responses to "OS X Games from the Ground Up: Building a User Interface with NCurses (1)"

Leave a reply