iOS For High School Students: Text Adventure Game

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 […] By .

Leave a rating/review
Save for later
Share
You are currently viewing page 4 of 5 of this article. Click here to view the first page.

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.