Adventure Game Objective-C Programming Activity

(Originated by Ray Wenderlich,Tutorials for iPhone / iOs Developers and Gamers,

Games are by nature interactive – users make decisions that affect the outcome of the game. This game is no exception. When a decision is needed, it will ask the user for a number (1 or 2). The user’s input will trigger a resulting series of events.

To give you a preview of what you’ll be building, here are the different components:

  • Get the user’s input (name, responses)
  • Allow the user to restart easily
  • Create plot/storyline (consisting of good/bad/neutral decisions)
  • Give the user a health rating that can change through the course of the program

Start up XCode and create a new project with the Mac OS X\Application\Command Line Tool template. Enter MyAdventureGame for the product name, leave the company identifier as is, select Foundation for the Type, and make sure Use Automatic Reference Counting is checked:

Save your project to a location that you will remember. Now switch to main.m in your XCode project navigator.

With the release of Xcode 4.2, Apple has added some neat new features for memory management called Automatic Reference Counting (ARC). Before this features you had to instantiate an NSAutoreleasePool, which held your memory, and then later release it (using [pool drain]) to free your computer for other tasks. Between the two screenshots below, note the difference in the structure of main.m.

The original main() function before ARC.

The new main() function utilizing ARC.

Automatic Reference Counting uses an autoreleasepool block. This determines which objects remain in memory based on pointers and references to the object as specified by the programmer.

The Framework

main.m will be the file where all of your information is brought together. This means that the meat of your program will be stored in header files (.h) and other implementation (.m) files. All you will have to do is import the right files into main.m and then execute some methods.

To set up the framework, add some new files.

Create a new file with the Mac OS X\Cocoa\Objective-C class template. Name the class Game, and make it a subclass of NSObject (You probably will have the sub-class already set to NSObject. If so, you don’t have to do anything).

Save the file to the project folder. You’ll generally have the project folder location come up by default.

In Game.h you should see the following:

#import <Foundation/Foundation.h>
@interface Game :NSObject
@end

In the Game.m file, you should see the following:

#import "Game.h"
@implementation Game
@end

The purpose of the Game.h file is to lay out the names of the different variables and methods that will be implemented as part of the Game class. The Game.m file is where you will actually implement each of the methods, constructing how they work.

Notice that at the top of Game.m the “Game.h” file is imported. This connects the interface with the implementation. In order to begin using the Game class in your program, you simply have to import the header file for the Game class into main.m.

At the top of main.m, right below the existing #import line, add the following:

#import "Game.h"

To clarify, Game is a class you will create to contain all the guts of the program. Ultimately, your program will create an instance of the Game class, and you’ll call it myGame. Game.h and Game.m are just the blueprint for this class. As you create the Game class, I’ll go into more detail.

For now, your first goal is to enable the program to receive input from the user. The standard way of receiving input via the keyboard is the scanf() function, which takes the next character that the user types and stores that character in a variable of type char.

scanf("%c", aCharVariable);

The %c tells the compiler to look for inputs that are of type char (this means that they are characters, not integers). When the program executes this line of code, the console will wait for the user to type in a character. If the user types x, then the value of aCharVariable will be set to x.

In this program, you’ll be using scanf() every time you need the user to enter an integer or character. Simply due to the nature of text-adventure games, where the plot is determined by user input, scanf() is going to be called a lot!

Because scanf() waits for user input, it can also be used to pause dialogue. For example, it would be better for the user to read a line, press enter, read a line, press enter, etc., rather than being given a whole paragraph to read at once. (The game needs to be interactive, duh!)

The First Method

You’ll begin the game by providing the user instructions on how to operate the game. To do this, you’re going to create a method called instructions. The instructions method needs to be a part of the Game class, and you have to declare it and implement it.

In order to make the method a part of the Game class, you include its definition under your Game class! First it will be declared in Game.h (the header file), and then it will be implemented in Game.m (the implementation file).

At the moment, your header file for the Game class is empty.In Game.h, modify the code to provide the method declaration for instructions so that your code matches the code shown below:

#import <Foundation/Foundation.h>
@interface Game :NSObject
{
//instance variables
}
-(void) instructions;
@end

First of all, notice that I included some curly brackets after the @interface line. This is where you define the variables specific to your class. Then, when you create an instance of the class, it will be an object that possesses the variables defined within the curly brackets - these variables are known as instance variables.

However, the objective at this point is to declare a method specific to the game class. That's what we've done with the -(void)instructions; line - it declares a method called instructions.

  • The "-" denotes that the method affects the instance and not the original class. For the time being, you will only be working with instance methods.
  • The "(void)" indicates that the method returns void, or nothing. In other words, the method might do something, but it does not return a value as a result of the method. In contrast, a "(BOOL)" in place of the "(void)" would indicate that the method returns a boolean, or a true or false value.
  • Lastly, "instructions" is simply the name of the method. Standard naming conventions for methods dictates that the first word should be lowercase while each additional following word begins with an uppercase letter. For example, if you wanted to change the name of the method to suggest instructions that occur only at the beginning, beginningInstructions or instructionsThatStartGame would be appropriate. This is not mandatory but contributes to the readability of your code and that is something that you should strive for.

Next, you have to implement the instructions method. Select the Game.m file. All you have to do is type in your method declaration again, but this time, include curly brackets to include the commands you want the method to execute every time it's called. The final Game.m should look like this:

#import "Game.h"
@implementation Game
-(void) instructions
{
//stuff you want the method to do
}
@end

Because it's important to be able to test and run your code after every major change, you need to get your application working before you do anything more. Switch back to main.m to actually initialize your game. Replace the "NSLog(@"Hello, World!");" line and the comment above it in main.m with the following:

// 1 - Initialize game
Game* myGame =[[Game alloc] init];
// 2 - Show instructions
[myGame instructions];

This is what the above code does:

  1. This line can be summarized as: “Create a pointer to an instance of the Game class named myGame and set its value to be a pointer to an instance of Game that has been created (allocated memory for and initiated).”
  2. This line has myGame run the instance method named "instructions". Recall that a pointer is not the actual object with its specific methods, it is just a reference to its memory location.

Build and run the program. With any luck, you should see output similar to the following, which confirms the program ran (and ended) appropriately.

It's also possible that, depending on your XCode set up and version, that you won't see anything at all output. As long as you don't see an error message, this should be fine as well.

Note: In case you're wondering why you might not see any output, one possible reason is that the above output is from the GDB debugger, which was used in older versions of XCode. The latest XCode versions set the debugger to LLDB by default and the LLDB debugger does not output anything if the program itself had no output. Since you still haven't written any code to output anything, the above will result in there being no output.

Now go back to Game.m - it's time to decide what you want the instructions method to actually do. For this tutorial, you want it to do the following:

  • Introduce the game scenario and ask for the player's name;
  • Allocate storage space for the player name;
  • Take input from the keyboard to and store it in the previously allocated storage space.

So the first step is to print a message to the console for the user to read. Within the curly brackets for instructions in Game.m, insert the following:

// 1 - Show introduction
NSLog(@"\n\nYour plane crashed on an island. Help is coming in three days. Try and survive.\nType your first name and press enter to continue.");

Just as a reminder, "\n" creates a newline. I chose to double up on them in the beginning to make the text easier to read in the console, because it can get cluttered with time stamps. Feel free to build and run the code whenever you make changes so you can detect any errors early on.

Now you need to create a variable to store the username so that you can use it throughout the game.

The example above uses NSLog() to print out text. More specifically, NSLog() prints out a bunch of characters. The double quotations take all the characters and the spacesbetween them and creates a string (a consortium of chars). It then proceeds to log (print) the string in the console.

You need to store the user's input as a string. So add the following to instructions below the NSLog line:

// 2 - Define storage for name
char firstname[20];

The above code creates an array, of characters - the word "char" denotes what type of variable you're creating to store the input and "firstname" is the name of the string of characters. A string is an array of characters. However, it is not true to say that all arrays are strings, because an array could be anything (an array of children, integers, zombies, etc.). A string is just a specific type of an array.

Note: An array is simply a collection of data where you can easily access each element by referring to an index. For instance, 10 people standing in a row would be equivalent to an array since you can refer to each individual in the line as #1, #2 etc. An array is pretty similar to that in computer terms.

Back to the code snippet above: the "[20]” indicates that the string can hold 19 chars or 19 different items (1st, 2nd, 3rd…20th).

The reason the array can hold 19 different characters instead of 20 is due to the fact that character arrays usually have a marker which indicates the end of the string. This marker is the null character and so if you had filled in every position in the above character array, the null character would go in the final (20th) position and so you can only fit in 19 characters in the array.

Now you need to use scanf() to receive input from the user. Add the following below your last line of code:

// 3 - Receive input via keyboard
scanf("%s", firstname);

The scanf() function is known for not handling whitespace (spaces, tabs, the newline character etc.). This line will only take the first chunk of characters before a space, which is perfect for the first name, if users follow the instructions and don’t try to type their full name. >:I

The “%s” denotes that firstname is a string, just like “%i” denotes an integer. Lastly, the & character used before firstname means “address of.” It directs scanf() where to store the value of the input. You will notice that the program doesn’t crash if you omit the &. However, it’s good coding practice to include it.

You now need to log a message to console. But you have a problem. NSLog cannot output our string! Specifically, it can’t log a C-string. The firstname string and scanf are code from the C language.

All you have to do is convert the C-string to an NSString and then NSLog will log it appropriately. Note that an NSString is actually an object – you are using an object-oriented programming language after all! Here’s how you accomplish the task – add the following code after your last code:

// 4 - Convert input to an NSString
NSString* name;
name =[NSString stringWithCString:firstname encoding:NSASCIIStringEncoding];

You create “name” as a pointer to an NSString object. You can then assign it the value of our C string converted to an NSString by the methods stringWithCString and encoding (standard methods in the NSString class).

Now you’re going to make a slight alteration to increase your ability to re-use the variable “name.” Because its declaration (NSString* name) is located in the instructions method, it can only be used within that method. This is where instance variables come into play.

If you declare “name” as a variable of your Game class, then you can use it anywhere within the confines of the Game class. So, remove the declaration from the instructions method and add it to Game.h – simply cut the first line in section #4 and paste it into Game.h within the curly braces as follows:

//instance variables
NSString* name; //Easy enough right?

Compile and run your code just to make sure you haven’t made any errors up to this point. If everything works, you should see something similar to the following:

You’re almost done with the instructions method. You just need to give the user some feedback to make sure they entered their name right. All you want to do is log the value of “name” with a cute little message.

The important thing to remember is that “name” is neither an integer nor a char, so (%i and %c) won’t work. The correct symbol is %@, which refers to objects. So add the following to the end of instructions in Game.m:

// 5 - Display name
NSLog(@"Your name is: %@. Press enter to continue.", name);

Note the absence of the & character. “Name” is already a pointer to an NSString object and a pointer is a reference to a memory address. So, you can see the redundancy of using &name or the (address of) name.

Compile and run and if you enter your name at the prompt, you should see something like this:

Next you’ll proceed to develop more of the framework before getting into the plot.

More Framework

You have already successfully enabled the computer to receive input from the user. Although you’re not entirely finished with this task, the next goal is to allow the user to restart easily. When I approached solving this problem, I wrote out explicitly what I wanted the user to experience.

User enters name, proceeds through dialogue, blahblahblah, die, try again,
User enters name, proceeds through dialogue, blahblahblah, die, try again,
User enters name, proceeds through dialogue, blahblahblah, die, try again,
and etc. ad infinitum.

I quickly realized that a while loop would be the tool to run the same statements over and over again.

Select the main.m file. You don’t need the initialization of your game to be repeated, so you want to create the while loop to encompass section #2 while leaving section #1 outside the loop. So replace section #2 with the following:

// 2 - Set up loop variable
bool gameIsRunning =true;
// 3 - Loop
while(gameIsRunning){
// 4 - Show instructions
[myGame instructions];
}

Here’s a step-by-step breakdown of the above code:

  • Our while loop will need a way to break out of the loop. You’ll be using a boolean-type variable to make the determination. We define and initialize that variable as gameIsRunning.
  • We set up the while loop to be dependent on the value of gameIsRunning. As long as gameIsRunning is true, the while loop will repeat.
  • We show the game instructions within the loop.

Now you have to create the dialogue to ask the user whether or not they want to restart. If they do want to restart, then you want the game to proceed accordingly and loop through the while loop.

You also want your users to be able to exit the loop and end the program. This can be accomplished by prompting the user for a response as to how they want to proceed after each call to instructions. To do that, we need a variable to store the response. Add that as follows to section #1 in main.m (right after the line initializing myGame):