Pick Your Own Adventure iPhone Edition

March 24th, 2010 Posted by: Collin - posted under:Tutorials

A few days ago I came across this cool Pick Your Own Adventure game on from user . I decided to make a quick renewable application that used UIAlertViews and UIActionSheets to tell the story. We are going to implement the UIAlertView and UIActionSheet protocol methods in order to get the functionality we are looking for. This will be a short project but will show the important methods associated with these views.

Step 1

Start a new view based project called CYOA. If you want to add a background image you can add a UIImageView to your CYOAViewController.xib. You can get the one I used here. But that is not necessary.

Step 2

In your project we are going to create a new object to hold the pieces that compose and given “frame” of a pick your own adventure. A frame of a pick your own adventure game is composed of 6 things as far as I am concerned.

  1. Prompt/Title
  2. Choice 1 Text
  3. Choice 2 Text
  4. Choice 1 Tile
  5. Choice 2 Tile
  6. isEnd

Through the use of these 6 objects we will be able to create a network of what I have called “AdventurePieces”. Create a new file. Make a subclass of NSObject and call it AdventurePiece.

Open AdventurePiece.h and input the following.

#import
 
@interface AdventurePiece : NSObject {
        NSString *message;
        NSString *choice1;
        NSString *choice2;
 
        AdventurePiece *choice1Piece;
        AdventurePiece *choice2Piece;
 
        BOOL isEnd;
}
 
@property (nonatomic, retain) NSString *message;
@property (nonatomic, retain) NSString *choice1;
@property (nonatomic, retain) NSString *choice2;
 
@property (nonatomic, retain) AdventurePiece *choice1Piece;
@property (nonatomic, retain) AdventurePiece *choice2Piece;
 
@property (assign) BOOL isEnd;
 
-initWithMessage:(NSString*)_message
         firstChoice:(NSString*)_choice1
        secondChoice:(NSString*)_choice2
          firstPiece:(AdventurePiece*)_choice1Piece
         secondPiece:(AdventurePiece*)_choice2Piece
                   isEnd:(BOOL)_isEnd;
 
@end

Step 3

We are now going to implement the initialization method that we defined in our header. Open up AdventurePiece.m and input the following.

#import "AdventurePiece.h"
 
@implementation AdventurePiece
 
@synthesize message;
@synthesize choice1;
@synthesize choice2;
 
@synthesize choice1Piece;
@synthesize choice2Piece;
 
@synthesize isEnd;
 
-initWithMessage:(NSString*)_message
         firstChoice:(NSString*)_choice1
        secondChoice:(NSString*)_choice2
          firstPiece:(AdventurePiece*)_choice1Piece
         secondPiece:(AdventurePiece*)_choice2Piece
                   isEnd:(BOOL)_isEnd
{
        if(self = [super init])
        {
                message = _message;
                choice1 = _choice1;
                choice2 = _choice2;
                choice1Piece = _choice1Piece;
                choice2Piece = _choice2Piece;
                isEnd = _isEnd;
        }
 
        return self;
}
@end

All we need to do in this initialization method is fill in our object parameters appropriately.

Step 4

Now that we have made the object that will facilitate our story we need to go about making the logic to automate bringing up the adventure pieces in the right order. First we are going to define one object in our view controller header. Go to CYOAViewController.h and input the following.

#import
#import "AdventurePiece.h"
 
@interface MakeMyOwnAdventureViewController : UIViewController <UIActionSheetDelegate, UIAlertViewDelegate>  {
 
        AdventurePiece *currentPiece;
}
 
@end

Step 5

With that done we need to do the following final steps.

  1. When the app launches a method should be called that creates a bunch of connected adventure pieces and sets the currentPiece instance variable to the one we desire to start on.
  2. Tell the app to show the currentPiece adventure tile.
  3. Respond to users pushing buttons on the adventure tile by resetting the current piece instance variable.

    Pull up a different view when the end of the chain is reached.

For our purposes we will be have UIAlertViews show the parts of the story before the end. And the final piece of our story will be shown in a UIActionSheet. Lets look at each of the code pieces required to finish off our last four steps.

1. When the app launches a method should be called that creates a bunch of connected adventure pieces and sets the currentPiece instance variable to the one we desire to start on.

-(void)makeTheStory {
 
        AdventurePiece *successPiece = [[AdventurePiece alloc] initWithMessage:@"Sparks fly from the red wire but no explosion! Kim Jong Il’s evil plot is foiled!" firstChoice:@"YAY" secondChoice:nil firstPiece:nil secondPiece:nil isEnd:YES];
        AdventurePiece *failPiece = [[AdventurePiece alloc] initWithMessage:@"Cutting the blue wire begins a chain reaction - omg that is bad. Like really bad..Kaboom! What went wrong!?!" firstChoice:@"Too Bad" secondChoice:nil firstPiece:nil secondPiece:nil isEnd:YES];
        AdventurePiece *failPiece1 = [[AdventurePiece alloc] initWithMessage:@"Bad Choice. You Die" firstChoice:@"Too Bad" secondChoice:nil firstPiece:nil secondPiece:nil isEnd:YES];
        AdventurePiece *middlePieceA = [[AdventurePiece alloc] initWithMessage:@"You parachute to North Korea, sneak past guards to a live nuclear bomb. Do you" firstChoice:@"Cut Red" secondChoice:@"Cut Blue" firstPiece:successPiece secondPiece:failPiece isEnd:NO];
        AdventurePiece *middlePieceB = [[AdventurePiece alloc] initWithMessage:@"As you are leaving you notice trained assassins behind you" firstChoice:@"Run" secondChoice:@"Fight" firstPiece:failPiece1 secondPiece:failPiece1    isEnd:NO];
        currentPiece = [[AdventurePiece alloc] initWithMessage:@"You are assigned to a dangerous mission." firstChoice:@"Accept" secondChoice:@"Vacation" firstPiece:middlePieceA secondPiece:middlePieceB isEnd:NO];
}

In this method we make 5 total adventure pieces. This specific pick your own adventure is quite short. It as 3 prompts, 2 choices each with a total of 4 end points. You can see what I mean in this diagram here. The white square are the prompts, the green squares are the choices and the red squares are the final results. We fill in the current piece instance variable as the top most piece and we are done here.

2. Tell the app to show the currentPiece adventure tile.

- (void)viewDidLoad {
 
    [super viewDidLoad];
        [self makeTheStory];
        [self showStoryForCurrentPiece];
}
-(void)showStoryForCurrentPiece {
 
        if([currentPiece isEnd]) {
                UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:[currentPiece message] delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:[currentPiece choice1],@"Retry",nil];
                [actionSheet showInView:self.view];
        }
 
        else {
 
                UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Your Message" message:[currentPiece message] delegate:self cancelButtonTitle:nil otherButtonTitles:[currentPiece choice1],[currentPiece choice2],nil];
                [alert show];
        }
}

This method should be called every time a new current piece is set. If checks to see if the adventure piece is an ending piece, if it is then an action sheet it created. If it is not an alert view is created. The button titles, and prompts are filled in appropriately.

Respond to users pushing buttons on the adventure tile by resetting the current piece instance variable.

- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex {
 
        if(buttonIndex == 0)
        {
                currentPiece = [currentPiece choice1Piece];
                [self showStoryForCurrentPiece];
        }
 
        else
        {
                currentPiece = [currentPiece choice2Piece];
                [self showStoryForCurrentPiece];
        }
}

When an alert view button is pressed this method will be called. An adventure piece that brings up an alert view should always have connected adventure pieces for both of the possible answers it as. This resets the currentPiece adventure piece and show the current piece again.

Pull up a different view when the end of the chain is reached

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
 
        if(buttonIndex == 1)
        {
                [self makeTheStory];
                [self showStoryForCurrentPiece];
        }
}

If this method is called that means that a action piece that was at the end of a chain was brought up. If the users hits the second button (button id == 1) that means they want to reset the story. This can be performed by building our story again showing the current piece again.

You can change the makeTheStory method to be whatever type of story you want. You can download the project here.

  • RoberRM

    Once again, thank you for a very well explained tutorial.
    I’d like to point out though than in Step 4 it reads &lt instead of .

  • RoberRM

    Well, the post was edited, but I’m talking about the &lt ; and &gt ; that should be lower than and greater than symbols.

  • John

    When you go on vacation, any button you click after that it tells you that you cut the blue wire and died. (source code). Needs some fixing.

  • http://www.rightsprite.com Collin

    Hey John,

    I see what you are saying and fixed the code in the post to make up for it. I made the “fail” adventure piece to specific. You can feel free to change all the text in the tiles to whatever you want to create your own. Thanks for reading and happy coding!

  • Daniel

    Greetings, Im totally new at developing for iPhone, and I was wondering what’s the best way to transfer the recently developed application to the iPhone?

    Thanks in advance!

  • http://fungineers.wordpress.com Jon

    Well this is annoying, just started developing my own choose your own adventure game a few weeks ago. Hope no one starts to think I’ve been stealing ideas.

  • Matt

    This project has a pretty obvious memory leak.

    In your actionSheet:clickedButtonAtIndex: method, you call [self makeTheStory] for the second time. This is an issue because the previously allocated AdventurePiece’s are still out there in memory, and are inaccessible since they were created as on the stack instances. There are two approaches to fixing this.

    1) Make the objects part of the autorelease pool by (I believe) changing the constructor of Adventure piece from:
    if(self = [super init])
    TO:
    if(self = [super autorelease])
    If I am wrong about that I’m sorry…

    2) Make the following changes to use self managed memory: (I did not compile this so it may need some tweaking!)
    In your AdventurePiece.m write the following destructor:
    - (void) dealloc
    {
    if(choice1) // If object != nil
    [choice1 release];
    if(choice2) // If object != nil
    [choice2 release];
    [super dealloc];
    }

    And then you must maintain a reference to the first piece to start the chain reaction of deletion. Add to CYOAViewController.h:
    AdventurePiece *startPiece;
    (And add it to the synthesize in the .m)

    Add to the makeTheStory method at the last line:
    startPiece = currentPiece;
    This copies the address so that when currentPiece changes the top level (starting) piece is saved.

    Then, finally, in the actionSheet:clickedButtonAtIndex: method, call:
    [startPiece release];
    Before you call [self makeTheStory]; that way the entire story is deleted and remade. The other way is to instead of releasing, you could just say currentPiece = startPiece; and reuse the same story, then don’t call [self makeTheStory], but either way works…

  • Liz

    Great tutorial, thanks for posting! I’ve modified it a bit but I was wondering, if I wanted to add more levels, how would I go about doing that? Would I create another set of variables like middlePieceA/middlePieceB? I’m new to this so forgive me if this is an obvious question. Thanks again for posting this!

  • Joe

    Hey guys, Amazing tutorial, How could I make this not show up all in AlertViews? could it be just text, if possible