iOS For High School Students: Text Adventure Game

Connor Osborn
A tutorial series about iOS for high school students - by high school students!

A tutorial series about iOS for high school students - by high school students!

This is the next tutorial in the beginning programming for iOS series – for high school students, by high school students! This tutorial will walk you through creating an app from the very beginning.

The tutorials are cumulative, so principles will build upon the first iOS For High School Students tutorial.

As in the first tutorial, you will create a Mac command line app to start simple. If you’re dying to get to the iPhone, don’t worry – the next tutorial in this series will be for an iPhone app! ;]

This time around, you’ll build a text-based adventure/survival game! You will take the role of a character crashed on a desert island, and your goal is to escape the island without being killed along the way.

In the process, this tutorial will cover the following topics:

  • Arrays
  • Classes
  • Objects
  • Methods
  • Properties

You’ve probably heard all of those words before, but not in the context of programming. You may be starting with no idea of what those words have to do with building apps, but trust me, you want to make friends with these concepts. And by the end of this tutorial, you will!

Getting Started

First of all, it would be a good idea to review the first tutorial in this series to jog your memory before continuing with this part.

All done? Then let’s get going with our game!

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.

Some text/adventure games have four options for the user to choose from (A, B, C, D). However, each additional option makes the game more complex, as you can imagine. :P

When I had the idea for the survival game, I took some time to write down the different parts of the program, as an outline. This approach will save you time programming. To give you a preview of what you’ll be building, here are the different components I listed:

  • 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

With the above in mind, let’s create our Xcode project!

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). If you recall, in the last tutorial you had to instantiate a 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 original main() function before ARC.

The new main() function utilizing ARC.

Without going into a lot of detail, 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.

For now, it is important to recognize the new structure. If you would like a beautiful explanation, click here.

The Framework

In order to make your program as clear to read as possible, 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. EASAAAY, right? 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 spaces between 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. And you thought you were learning Objective-C (the joke’s on you). [trollmeme]

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).

When I was trying to solve this problem on my own, I used Apple’s Developer Library Reference. I was hesitant to use the Developer Library Reference in the past, because it isn’t exactly “uber” user friendly for beginners. However, I really encourage you to take a peek.

If programming ever becomes overwhelming: just relax. You will always be running into new problems to solve. Just realize that everything you read/learn is more stuff that you know and less that you have to be confused about. :P Give this reference a whirl!

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.

As a little joke, I created a version of this program for a friend. I told her to type in my name. However, I had created the program to return with the message “No, your name is…calculating…calculating…searching Google…: Sarah.” To which she responded:

Oh the humble joys of programming!

Anyway, cheers on getting your first method working! 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 initialise 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.

However, if they find the game to be laaamme, then you want them 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):

        int response;

Now get the user response via the following block of code, which should be added to main.m directly below section #4:

// 5 - Quit option
NSLog(@"\n\nRestart?\n1.Yes\n2.No");
scanf("%i", &response);
if (response==2) {
    break;
}

First, try and understand what the above code means, before reading the summary below.

The function NSLog() provides the user with guidelines on what to enter as a valid response to the question. Then scanf() prompts the user for an integer that will store the value into the variable response. The if statement checks to see if the user answered 2 (which means end this misery). When the user presses 2, the statement break is evaluated, and the program exits the while loop.

Give the program a run to check for accuracy!

As you run the code, you ought to notice that there is currently no way for the dialogue to wait until the user has pressed enter. In the next section, you’ll solve this with the use of a helper function/method.

A Helpful Function

As you might have inferred, you will now be creating a function which will truly be helpful in our program. Although it might not immediately appear useful, just you wait.

Like the instructions method, our function needs a declaration and an implementation. As was true with the instance variable you created above, a function’s declaration location (either inside instructions or the entire game itself) has implications as to where it can be utilized.

To begin, you will declare this function in Game.h, so that the definition can be imported like the Game class. However, note that while the declaration will be in Game.h, the function will actually be independent of the Game class definition (denoted by @interface). Your function will be called waitOnCR (wait on carriage return).

Here is the waitOnCR declaration (a.k.a. its prototype), placed above the Game class definition (above @interface) located in Game.h:

void waitOnCR (void);

The first “void” indicates that the function’s return type value is of type void (basically nothing). You already know the name, and the second void in parentheses is the argument or parameter the function takes (again basically nothing).

And here’s the waitOnCR implementation, placed above the main() function and below the two imports, located in main.m:

void waitOnCR (void) 
{
    while( getchar() != '\n' ) {
    /*flush line buffer*/} ;
 
}

This waitOnCR function is really simple: it has no input or output, just a single task. The implementation might be a little confusing. In order to understand it, you’re going to have to learn a little bit more about input, scanf(), and getchar().

Whenever the user types into the console, the characters are stored in a memory buffer. The function scanf() reads values from that buffer, storing the values to a variable, as directed by the programmer. However, different input functions handle whitespace (spaces) differently.

When scanf() reads input, it only takes input up until it sees a whitespace character. If you were to enter “f f fj sjk”, only the first f would be stored in the variable.

Another function for getting input is called getchar(). It operates by taking the next character from the input buffer and storing it to any variable that is indicated. You will have to consider how both operate in order for waitOnCR() to function as intended.

Every time scanf() is called, an enter is retained in the buffer, because enter must be pressed for the input to be considered. Any data left over in the input buffer gets stored into the variable with the next scanf() call. So, if you typed in “f f” and then pressed enter, only the first “f” would be stored in the specified variable but the second “f” will get stored on the next call to scanf().

Generally, a call to scanf() will wait for input, because the input buffer is waiting for input to be entered. However, any remaining input in the buffer will be taken as an argument to the next scanf() call. This has the result of totally bypassing the second call that should be waiting for the user to enter data. In order to solve this issue, the input buffer needs to be cleaned/emptied.

The while loop in waitOnCR() uses getchar() to check for that leftover input. A summarization of the while loop would be, “While the next character in the input buffer is not an enter, do nothing repeatedly until it is flushed.” By flushing the input buffer, the next call to scanf() will not be bypassed.

If this needs further clarification, feel free to drop a comment below. For now, it’s time to begin to make use of waitOnCR(). As you noticed earlier, the user never gets a chance to press enter after the log: “Your name is ######. Press enter to continue.” All you need to do is update your instructions method to include a call to waitOnCR(), right after section #5.

    // 6 - Wait for CR
    waitOnCR();

Properties

Properties are a concept of object oriented programming. A class can have certain attributes and these attributes are known as properties. In Objective-C, property values are stored in instance variables. In the early days of Objective-C, methods had to be created for every property to adjust their value. Each property typically would have a getter method to retrieve its value, and a setter method to change its value.

Because these methods are repetitive and nearly identical for most properties, synthesis was created. Now, properties have to be declared and synthesized. Once they’ve been synthesized, the property can be referenced via its class instances to return a value (the original getter method), or to change its value (the original setter method).

For example: myGame.health = 25; would store the value 25 to the property health. In a little while, you will create a property named health. But first, let’s convert our existing name instance variable (in the Game class) into a property. Add the following code after the closing curly brace (and above the -(void)instructions; line) in Game.h:

@property (strong) NSString* name;

As you will notice, with the exception of the “(strong)” statement, it is defined exactly the same as the instance variable located above in your code.

Strong refers to how name is stored in memory. In iOS 5, with the new ARC (Automatic Reference Counting) memory management, the compiler automatically determines when something should be deallocated (destroyed, deleted) or preserved. The parameter strong explicitly retains the object in memory. You will notice that none of the other properties you create for this game will have a parameter. That’s because “name” is the only pointer to an object. For now, be content with not fully understanding ARC in its entirety, as it is not crucial to this game.

Next, finish creating the “name” property by synthesizing it. All you have to do is synthesize name in Game.m right below the @implementation line and right above your first method definition:

@synthesize name;

In keeping with maintaining good coding practices, in this tutorial you’ll turn all of your instance variables into properties. I recommend a new line for each @property directive, but @synthesize can handle them all in one line separated by commas. For example:

@synthesize name, score, health, etc;

Before moving on to the most exciting part of the game – the plot! – you might want to compare your source code to the current state of the project by downloading the PDF located here to make sure that everything is on track.

More Game Structure and the Plot

The main idea of this text-based adventure game is survival. As the instructions text explains, the user has three days until rescue, during which they must survive. Programmatically, each day will be represented by a different method (day1, day2, and day3). The first two days will have two scenarios, while the third day will be a bit of an exception.

The plot will throw different scenarios at the user, where they will have to decide (by choosing 1 or 2) the most logical option. Although, as a bit of true-to-life irony, the most logical reasoning will not always result in the right solution in this game. :P

Now you must do just a little more framework building before jumping into the plot. First of all, create your methods for the various days. Go ahead and try creating them yourself (remember: declare and implement). They will be identical in body to the instructions method.

The declarations of day1, day2, and day3, located below the instructions declaration in Game.h would be as follows:

- (void) day1;
- (void) day2;
- (void) day3;

The implementations of day1, day2, and day3 located below the instructions implementation in Game.m:

-(void) day1
{
 
}
 
-(void) day2
{
 
}
 
-(void) day3
{
 
}

Now you need to add the ability for the player to die. If he dies during Day 1, you don’t want him to go on to Day 2; rather, you want the user to respond to the restart dialogue. So you need to create a condition where the player enters the next day only if he is alive.

Create a new boolean instance variable called userIsAlive, located below name in Game.h:

BOOL userIsAlive;

Now, add the @property declaration to Game.h:

@property BOOL userIsAlive;

Finally, add the @synthesize directive to Game.m:

@synthesize userIsAlive;

Each of your days now needs to feature a check at its beginning to see if the user is alive. To do this, create a while loop inside of each day method. Note that day1 is unique, because the user should always be alive at the onset of the first day! So, at the beginning of day1, set the value of userIsAlive to true by adding the following to the top of day1 in Game.m:

    userIsAlive = true;

Next, add the following code to each day method – in the case of day1, the code would go below the line we added above:

    // 1 - Loop
    while (userIsAlive) {
        // 2 - Statements for the day
    }

As a side note, the condition “userIsAlive” is the same as “userIsAlive==true” because userIsAlive is a boolean, so when the value of the variable is true, the condition evaluates to true. To represent false for the condition, “!userIsAlive” would be sufficient, where the “!” means not.

Now each day’s statements need to affect the user’s character in the game in some fashion. For example, kill the user, slightly benefit him/her, or slightly hurt her/him. For this to happen, you need to create a property called health. Add the instance variable to Game.h first:

int health;

Next add the @property declaration to Game.h:

@property int health;

And finally, @synthesize the property in Game.m:

@synthesize health;

Note that health will have a numerical value, and is of type int. With health, you’re going to make use of the property by assigning health a value of 100 when Day 1 begins. Add the following code to the end of section #1 in main in main.m:

myGame.health=100;

The above is the starting health for the user that will change throughout the game.

Also, after every scanf() call, the user’s input needs to be stored into a variable. For this, create an integer instance variable (it will always be 1 or 2) called ans in Game.h:

int ans;

Declare it as a property:

@property int ans;

And finally, synthesize it in Game.m:

@synthesize ans;

This next bit of code will be the boiler plate method for all three days (day3 will be different at the end). Copy the comments below into each day method, replacing the existing section #2:

        // 2 - Scenario1
        // 3 - Answer1
        // 4 - Scenario2
        // 5 - Answer2
        // 6 - Day Completion Message

Lastly, you want to execute all three day methods (although they haven’t been defined yet). In main.m, add the following code to the end of section #4 (beneath the execution of instructions):

[myGame day1];
[myGame day2];
[myGame day3];

Looking at the code so far, nowhere in your code yet is the user told they have died. Sure, the user gets kicked out of the while loops, and is asked if they would like to restart, but we have to make sure the user knows they’re dead. (X_X)

We do that by using the health property to check if the player’s health is less than or equal to 0. Copy the following code to the end of section #4 in main.m right before the restart prompt:

if (myGame.health<=0) {
    NSLog(@"\n\nYou have died.\nPress enter for the next dialogue.");
    waitOnCR();
}

At this point, I’m going to progress quickly through the plot (that I spent hours creating and noodling over :P). If all these concepts seem pretty straightforward to you and you would like to create your own plot, go straight ahead! However, I will provide my plot for those who want something they know will be free of errors.

I recommend building and running the program frequently, as you add the plot elements, to minimize the number of errors that surface.

Add each of the following code blocks to the relevant section (2, 3, 4, 5, or 6) in each of the day methods in Game.m.
Day 1: Scenario 1:

NSLog(@"\n\nDAY:01\n\nWith blurred vision, you look around to gather your surroundings.\nYou are lost.\nThe beach is rough, and the salty water is irritating your skin. You recall the plane crash.\n1.Walk into the jungle.\n2.Sleep and regain strength before walking into jungle.");
scanf("%i", &ans);
waitOnCR();

After a cute little intro, options 1 and 2 are presented. scanf() stores the user’s response in the instance variable ans.

Day 1: Answer 1:

if (ans==2) {
NSLog(@"\n\nYou feel well rested. However, you are hungry and freezing without shelter. It is too dark to build a shelter. You try to sleep in a tree, but you are attacked by monkeys.\nPress enter to continue.");
            waitOnCR();
            health = 0;
            userIsAlive = false; //this prevents the while loops in day2 and day3 from functioning
            break;
		}

This provides a response for ans == 2. Note the first use of waitOnCR(). The user dies, and health is set to: 0, which prevents the user from progressing through any other days, and break kicks the user out of the while loop right away.

Day 1: Scenario 2:

NSLog(@"\n\nInside the jungle, you find a bush with little black fruit, at its base are white speckled mushrooms.\n1.Eat mushrooms.\n2.Eat berries.");
scanf("%i", &ans);
waitOnCR();

Note that the user moves into this scenario automatically after choosing the first option from the last scenario. The text provides the setting of the jungle and options 1 and 2 are again presented. scanf() stores the user’s response in the instance variable ans.

Day 1: Answer 2:

if (ans==1) {
            NSLog(@"\n\nYou are left with a bitter taste in your mouth and a gurgling in you stomach.\nHealth decreased.\nPress enter to continue.");
            health= health - 10; 
            waitOnCR();
        }
        else {
            NSLog(@"\n\nThe sweetness of the berries is quite savory considering all the seawater you consumed.\nPress enter to continue.");
            waitOnCR();
            health = health + 10;
        }

This provides a response for ans == 1 and for else (if they answered 2). The first case subtracts 10 from the player’s health. The second case increases the value of health by 10.

Day 1: Completion:

NSLog(@"\n\nYou fall asleep beneath the tree. Day:01 was successfully completed.\nPress enter to continue.");
waitOnCR();
break;

At the end of the day, a congratulatory message is logged. Once the user presses enter, the user breaks (leaves) the while loop for day1 to proceed to day2.

Before you proceed to day2, run your program and check it against the completed implementation for the day1 method located here.

Day 2: Scenario 1:

NSLog(@"\n\nDAY:02\n\nUpon being nudged by a large animal, you awake.\nIt is a large gorilla.\n1.Run.\n2.Embrace your fate, and climb into its mouth.");
scanf("%i", &ans);
waitOnCR();

After another cute little intro, options 1 and 2 are presented. scanf() stores the user’s response to instance variable ans.

Day 2: Answer 1:

  if (ans==1) {
            NSLog(@"\n\nThe gorilla watches you run in amusement. It trails behind you, until you drop from exhaustion.\nHe takes a bite out of your scrawny arm.\nPress enter to continue.");
            waitOnCR();
            NSLog(@"\n\nOUCH!\nThe gorilla shakes its head in disapproval and proceeds to spit out the remaining flesh. The gorilla runs back into the jungle.\nYour gaping wound results in a significant health decrease.\nPress enter to continue.");
            waitOnCR();
            health= health - 60;
        }
        else {
            NSLog(@"\n\nBy peacefully approaching the gorilla, it does not feel threatened. It takes you on its back to explore the island.\nPress enter to continue.");
            waitOnCR();
            NSLog(@"\n\nAs you pass through the jungle, you see that snakes inhabit the taller trees.You pass through a grand meadow of grass.\nThe ape slows, as you look to the east coast of the island, you make out a tribe of warriors. The ape turns and brings you back to where he found you.\nYou take a mental note of what you saw.\nPress enter to continue.");
            waitOnCR();
        }

This provides a response for ans == 1, which results in a significant health decrease. In the else case (ans == 2), the user gains some interesting information.

Day 2: Scenario 2:

NSLog(@"\n\nYou feel slightly chilled. You know the night will be really cold if you don't build a fire and retrieve wood.\n1.Search the beach.\n2.Search the jungle.");
scanf("%i", &ans);
waitOnCR();

After presenting the scenario of a cold night, options 1 and 2 are offered. scanf() stores the user’s response to instance variable ans.

Day 2: Answer 2: Beach:

	if (ans==1) {
            while (ans==1) {
                NSLog(@"\n\nYou feel the warm sand under your feet.\n1.Search the west coast.\n2.Search the southern coast.");
                scanf("%i", &ans);
                if (ans==1) {
                    NSLog(@"\n\nYou walk for a mile, and come upon some sun dried timber. You create a tee-pee structure out of the wood.\nYou manage to make the kindle catch fire by scratching rocks together.\nPress enter to continue.");
                    waitOnCR();
                    break;
                }
                if (ans==2) {
                    NSLog(@"\n\nYou walk in circles for another mile, this part of the beach does not have timber.\nPress enter to continue.");
                    ans=1;
                    waitOnCR();
                }
            }
        }

This provides a response for ans == 1. A while loop is created to make the user move between two options on the beach. The wrong choice forces the user to try again.

Day 2: Answer 2: Jungle:

Still on section #5 (Answer2), we need to add the response for and == 2:

        //Scenario 2 Answer Jungle
        else { 
            NSLog(@"\n\nYou find plenty of wood, and drag it on to the beach for your fire.\nPress enter to continue.");
            waitOnCR();
            NSLog(@"\n\nAs night falls you have failed to light your fire, the wood from the jungle is too moist and rotten.\nYou find shelter under another tree, but you are exceptionally cold and weak. Health decreases.\nPress enter to continue.");
            health = health - 10;
            waitOnCR();
        }

The wood in the jungle is moist, and the player loses health because of no fire.

Day 2: Completion:

NSLog(@"\n\nYou fall asleep. Day:02 was successfully completed.\nPress enter to continue.");
waitOnCR();
break;

Another congratulatory message is logged. Once enter is pressed, the user breaks (leaves) the while loop for day2 and proceeds to day3.

Before you proceed with day3, build and run your program and check with the completed implementation for day2 located here.

You’re almost done with your program. The method day3 will be just like the other two days, except it will be slightly longer, and it will need a local variable called “ate_granola”, which will be a boolean.

Because you only need ate_granola inside of this method, you don’t have to declare it as an instance variable of the entire game. Define ate_granola above section #1 in day3.

BOOL ate_granola;

Day 3: Scenario 1:

NSLog(@"\n\nDay:03\n\nThe sun bears down on your face and the sound of seagulls wake you. As you attempt to stand up, you are reminded of your ravaging hunger.\nPress enter to continue.");
        waitOnCR();
        NSLog(@"\n\nTo your surprise, you discover a granola bar, you stashed in the lower pocket of your cargo pants before your flight.\n1.Eat it.\n2.Save it for later.");
        scanf("%i", &ans); 
        waitOnCR();

After the requisite cute intro, options 1 and 2 are presented. scanf() stores the user’s response to instance variable ans.

Day 3: Answer 1:

 if (ans==1) {
            ate_granola = true;
            NSLog(@"\n\nThe granola bar effectively curbs your hunger. Health is increased.\nPress enter to continue.");
			health = health + 10;
            waitOnCR();
        }
        else {
            ate_granola = false;
            NSLog(@"\n\nPerhaps you will find a greater use for the bar later.\nPress enter to continue.");
            waitOnCR();
        }

If option 1 was chosen, health increases. Otherwise, the response implies that there might be a greater use for the bar in the future.

Day 3: Scenario 2:

NSLog(@"\n\nYou recall that today is the day the dispatchers promised a rescue. You need an aerial view of the island, to see where the rescue team will come.\n1.Climb a tall tree.\n2.Climb a rock face.");
scanf("%i", &ans);
waitOnCR();

As usual, the scenario is described, options 1 and 2 are presented, and scanf() stores the user’s response to instance variable ans.

Day 3: Answer 2:

        if (ans==1) {
            NSLog(@"\n\nYou climb to the top of a mossed over tree. The view is great and you can see a meadow that would be ideal for a helicopter landing.\nHowever, you also see a perfect area for a boat rescue on the east coast.\nAs you take in the view, you feel a jab and a following stinging sensation.\nYou have been bit by a snake.\nPress enter to continue.");
            waitOnCR();
            NSLog(@"\n\nAfter you climb down the tree, you faint at its base. Health is decreased significantly.");
            waitOnCR();
            health = health - 50;
            if (health<=0) {
                break;
            }
 
        }
        else {
            NSLog(@"\n\nYou climb to the top of a rock face. The view is great and you can see a meadow that would be ideal for a helicopter landing.\nHowever, you also see a perfect area for a boat rescue on the east coast.\nPress enter to continue.");
 
        }
        NSLog(@"\n\nYou decide to walk to the east coast as it seems to be the most likely rescue point.\nPress enter to continue.");
        waitOnCR();
        NSLog(@"\n\nAs you walk through the jungle, you trip over a net. You are instantly flung upside down.\nPress enter to continue.");
        waitOnCR();
        NSLog(@"\n\nAs you look around, all you can make out are dark forms with bright painted faces. You are surrounded by natives.\nPress enter to continue.");
        waitOnCR();
        NSLog(@"\n\nThey take you to the east beach, and set you next to a grand fire. When they are not looking, you reach into your pockets for something useful.\nPress enter to continue.");
        waitOnCR();
        if (ate_granola==true) {
            NSLog(@"\n\nYou search to find your granola bar wrapper, but you seem to have left it under a tree. You imagine that the shiny foil wrapper would have deeply impressed the natives.\nThe natives knock you out.");
            health=0;
            break;
        }
        else {
            NSLog(@"\n\nYou pull out your granola bar and draw their attention. They are shocked by the foil wrapper. They think it is a new metal.\nThe chief accepts your invitation to eat the honey granola bar. He is deeply impressed.\nPress enter to continue.");
            waitOnCR();
            NSLog(@"\n\nIn this same moment, a large steel rescue boat approaches. The natives are terrified and leave you standing on the beach.\nYou are rescued.\nPress enter to continue.");
            waitOnCR();
            NSLog(@"\n\nYOU WIN\nPress enter to continue.");
            waitOnCR();
            break;
        }

If the user choses option 1, they gain some knowledge, but get bitten by a snake, and health decreases. Else, they simply gain some knowledge. In either case, the game ends with a lot of text. Whether or not the user chose to eat the granola bar earlier in the day determines whether they live (and win the game) or die.

In case you’re unsure about some of the formatting, compare the day3 method against the code here. Note that waitOnCR() is called after every scanf() to clear the input buffer, and to prevent the next scanf() call from being skipped.

Finishing Touches

Your game is fully functional, and very near completion. However, why not add two methods to make a truly ~classy~ app.

The first method will be used to print the user’s health. You can then call this method after every day passes to provide the user with a health update. The second method will be purely for educational purposes (not super-classy).

Declare a method called printHealth in Game.h:

-(void)printHealth;

As you’ll notice, the method has no return value or any parameters, so it will look like every other method you’ve created up until this point. The function just needs to log the player’s name and print their health. Do that via the following code added to the end of Game.m (above the @end):

-(void) printHealth 
{
    if (health>0) {  
        NSLog(@"\n\n%@ managed to finish the day with a total health of: %i\nPress enter to continue.", name, health);
        waitOnCR();
    }
}

Note that I included an if condition in the above code because I thought it would be strange to log a user health of 0, especially when they should have died. Call printHealth either at the end of day1, day2, and day3 or after each call to day1, day2, and day3 in section #4 of main in main.m.

Build and run to test!

Your final method is unique because it takes a parameter. You want to create a method that logs the player’s score based on what percentage of health points they get out of the overall possible health points. The parameter will be how many health points the user received.

Create another integer instance variable named “score” in Game.h:

int score;

Add a property declaration for it:

@property int score;

And synthesize it in Game.m:

@synthesize score;

The method will be called printScore(x). Add the declaration to Game.h:

- (void) printScore: (float) x;

You’ll note that while the method returns void, it takes an argument x, which is a variable of type float.

And here’s the implementation of printScore(x) in Game.m (add it to the end above @end):

-(void) printScore:(float) x
{
    score = (x/110) * 100;
    NSLog(@"%@ based on your health you received %i percent of the available points.", name, score);
    waitOnCR();
}

In order to use the above method, you have to call it and provide the number of health points (x) received and since the health points is available via the health property, we can simply pass that. Try calling the printScore: method right before section #5 in main in main.m as follows:

[myGame printScore:myGame.health];

Where to Go From Here?

This concludes the second tutorial in the beginning programming for iOS series for high school students. Click here to download the full project.

I hope you come away from this tutorial with a basic understanding of arrays, classes, objects, methods and properties. If you feel confident, try messing around with the text-based survival game format provided here to create your own scenarios. For example:

  • If the game went on long enough, can you make it so that the user could lose enough health cumulatively to die? You could try subtracting a small amount of health every morning of each new day, for example, to account for the effects of lack of food and exposure to the elements.
  • Try implementing dialogue into the game, where the user is given choices of how to respond to, say, a talking monkey or a native who knows English.
  • Create a variable that can store food or water the user finds on the island, so that the user has a way to increase health if it gets dangerously low. Give the user the choice of consuming the food or water after each decrease in health.

In the meantime, I’m looking forward to reading your feedback in the forums. And stay tuned: the next tutorial will be an iPhone app!

User Comments

15 Comments

  • Hey guys! Great tutorial. So I had one quick question...

    Say you were doing something similar on the iPhone with a UITextField. If you have a loop running and need to capture the user response, how would you "stop" the while loop temporarily? What I mean is the scan function you would be using waits until it gets a response then continues code execution, how could be recreate that on an iOS app?
    coffeejay
  • Things work a little differently on the iPhone.

    When you're making a command line app like this, everything runs one line at a time and when you look for input, the program "waits" until the input is received.

    When you're making an iPhone app on the other hand, it's usually Apple's code that is running, waiting for events (like touches or the user tapping letters on the keyboard). When these events happen, they call methods inside your code to do things (such as receive notice that the user entered something into a text field).

    So if you were making this text adventure game on the iPhone, it would probably have this kind of logic instead:

    - Present Day 1 in a text area, present options to user, maybe have buttons to allow choose the option they want.
    - User taps button, your code gets a callback. We know we're in Day 1, so we set the text according to whatever the user chose, and now we're onto Day 2. Repeat.

    Making an iPhone version of this tutorial might be a good #3 in the series btw :]
    rwenderlich
  • Great tutorial, I wonder why it hasn't got more response!

    It would be nice if we could turn it into a visual iphone game, text is a little boring, but I imagine if you combined the text with cool images that fit the story, it would be a lot more fun :)

    I'm way too inexperienced to do that myself, I tried adding some visuals but I failed miserably :oops:
    David-T
  • I totally agree with David-T and hope we can get a tutorial for how to make a text and image-based adventure game! I'm totally new to programming and found a lot of tutorials hard to follow but these iOS For High School Students tutorials are just perfect! They explain everything in a really easy to understand way, they're fun to read and follow along with, and now I feel more comfortable doing other things with Objective-C. Awesome job on these tutorials. I hope you write more of them soon.
    lisarooj
  • This was a great tutorial! It really helped a beginner like me to get a little more familiar with programming :) I was wondering though.... Will there be any more "IOS For High School Students" videos? It would help a lot! :)
    Luke Stonehocker
  • As I was looking up what properties are and into (strong) more I came across some other sources that say when using immutable strings you should use (copy). I went ahead and tried to add that into to the "@property (strong) NSString *name;" line and xcode taught me something else new... copy and strong are mutually exclusive.

    So my question is if there is a specific reason I should use strong in this program instead of copy?
    Allen555
  • Hi!
    Nice tutorial to begin with. Though there are 2 things I came across:

      (1.) After the first scan (waiting for the user name input) the memory buffer in the code is not emptied by the function waitOnCR. Thus, the desired behavior is still not complied, since the code will prompt the restart/exit dialog without waiting for the user to press enter to continue. See:
      While the next character in the input buffer is not an enter, do nothing repeatedly until it is flushed.



      (2.) Concerning properties, as far I know, you don't have to declare variables in the instance variables section when they are already declared as properties.
    crishushu
  • I can't get the waitOnCr() function to work.
    it seems to drop out of the loop as soon as it hits the "while(getchar() !='\n' ){" line.

    I'm using xcode 4.5.2 Has something changed since the tutorial was written?

    main.m


    ...
    # import "Game.h"

    void waitOnCr (void)
    {
    while( getchar() != '\n' ) {
    /*flush line buffer*/};
    }

    int main(int argc, const char * argv[])
    ...
    birdman69
  • Hi,

    I am having the same problem.

    waitOnCR()

    isn't working as it is supposed to.
    si5584
  • UPDATE!

    For anyone having problems with the same issue as myself. I have updated the waitOnCR command to look like follows:

    =============================================
    void waitOnCR (void)
    {
    for (int i=0; i<2; i++) {
    while( getchar() != '\n' ) {
    /*flush line buffer*/} ;
    }

    }

    =============================================

    The added for loop basically only allows it to run once, therefore the second time the user hits enter, the program continues onto the next step.
    Hope that helps.

    Cheers,

    Si5584
    si5584
  • Where is the third part of the tutorial.... :?: :?: :?:
    trnsapra
  • Hi,

    I have changed the waitONCR function to look like the one below. I just added a getchar() before the while loop in this case to clear that last \n character that's in the input buffer. With this character in the buffer, the program will never enter the while loop, since there actually IS a \n character in the buffer (this is from the previous scanf function which asked for your name, the ENTER key would have been pressed).

    So, for the program to enter the while loop, the buffer is cleared by just extracting that existing "\n" with getchar().

    Code: Select all

    void waitONCR (void)
    {
        getchar();
       
        while ( getchar() != '\n' ) {
             //flush line buffer
        };
    }



    I hope this helps.
    I really have learned a lot from these tutorials so far.
    mich1891
  • the objective-c class option no longer exists, what should i chose now because none of the options give me the .h .m and main files like this tutorial has
    reese66
  • Please update this with the latest version of XCode. I can't get it to work, and the downloaded version says that the scanf of a char using %c is invalid... :?
    DKL

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Vote for Our Next Tutorial!

Every week, we alternate between Gaming and Non-Gaming tutorial votes. This week: Non-Gaming!

    Loading ... Loading ...

Last week's winner: Best iOS Animations in 2014. [Read Now]!

Suggest a Tutorial - Past Results

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in October: Xcode 6 Tips and Tricks!

Sign Up - October

Our Books

Our Team

Tutorial Team

  • Pietro Rea
  • Matthew Morey

... 49 total!

Update Team

... 15 total!

Editorial Team

... 22 total!

Code Team

  • Orta Therox

... 3 total!

Translation Team

  • Team Tyran
  • David Xie

... 32 total!

Subject Matter Experts

  • Richard Casey

... 4 total!