CPSC 91 Spring 2011: Lab 02


Introduction

In this week's lab, you will create an app that plays a simplified version of Craps. You are welcome to extend the app, but be sure that your core functionality works. In addition to making sure everything works, your code:

You will create a new project based on a View-Based Application. Call this project Craps. This will create a controller (CrapsViewController.h/.m) and a view (CrapsViewController.xib) . You should add a new file (Cocoa Touch Class, Objective-C class, subclass of NSObject) called CrapsModel that will represent the model.

Rules of Craps

You will be implementing a simplified game of Craps. Craps is a gambling game involving two dice. You begin by providing an "ante", the amount of money you are gambling on this game of Craps. If you win the game, your bank account goes up by the amount of your ante. If you lose the game, your bank account goes down by the amount of your ante.

The game is broken into two phases. Phase one is your first roll. If the sum of the two dice you roll is 2, 3, or 12, you lose your bet and the game is over. If the sum is 7 or 11, you win your bet and the game is over. For all other sums (4, 5, 6, 8, 9, 10), you continue to phase two. In phase two, you roll the dice again. If the sum of the dice is equal to your initial roll from phase one, you win and the game is over. If the sum of the dice is equal to 7, you lose and the game is over. For all other sums, you repeat phase two.

One piece of terminology from the game: if you don't lose (2,3,12) or win (7,11) on your first roll, the sum of the two dice is known as your "point". In phase two, you are trying to roll the "point" again before rolling a 7. In phase one, the value of the point is "off".

MVC Overview

Following proper MVC design, we will store all of the data and the logic for the game in CrapsModel. This means that data such as the player's current balance, the amount of their ante, the value of the dice they rolled, and the value of the point will all be part of the CrapsModel. In addition, the CrapsModel will have the logic of the game, such as determining if the player won or lost, setting the value of the point, and adjusting the player's balance depending on the outcome of the game.

The CrapsViewController will need to ask the CrapsModel for values that it needs to display in the view (created in the .xib file). So, you will need getter methods in your model that allow the controller to retrieve items such as the player's balance, the faces of the two dice, the status of the point, and the player's current status. The controller will also need to ask the model if the user won or lost so that it can display a message when that happens.

The model: CrapsModel

Your model will have the following API:

@interface CrapsModel : NSObject {
@private
    int die1, die2;  // the value of each rolled die
    int point;       // the value of the point; 0 if it is OFF
    int ante;        // the player's current ante
    int balance;     // the player's current balance

    BOOL won;        // YES if the player just won; NO, otherwise
    BOOL lost;       // YES if the player just lost; NO, otherwise
}

- (void)playTurn;    // called by the controller each time the player rolls the dice
- (void)reset;       // called by the controller when the player needs to start over
                     // because they went bankrupt

// These properties allow the controller to read the value of:
@property (readonly) int balance;  // the player's balance
@property (readonly) int point;    // the current point

// This property allows the controller to get and set the ante
@property int ante;

// These properties allows the controller to find out if the player just won or lost
@property (readonly) BOOL won;
@property (readonly) BOOL lost;

// These two properties are interesting because there is are instance variables
// named bankrupt or dice.  Therefore, you won't @synthesize these.
// Instead you'll write your own implementation.  
@property (readonly) BOOL bankrupt;
@property (readonly) NSArray* dice;

@end

Notice that you don't want the controller changing the values of your internal data, so nearly every property is readonly. The only data that the controller should need to change is the ante when the user adjusts the ante slider in the UI. However, you don't want to set the player's ante to whatever the controller says; rather, you want to validate the value of the ante to be sure it is greater than 0 and less than or equal to the player's current balance, setting the ante appropriately. Also, you don't want the user changing the ante in the middle of the game, so the ante should only change if the point is OFF (indicating a game is about to start). This means that although you can @synthesize ante, you'll still want to write your own implementation of -setAnte:

(Side note: A nice touch would be to have your model only allow ante's that are multiples of $5. This is not required, but it's also quite straightforward.)

Your model has two other methods in the API: -playTurn and -reset. The controller calls the model's -playTurn method when the user presses the "Roll" button. (A turn includes rolling the dice once, setting the point if necessary, determining if the player won or lost, and updating the balance if necessary.) The -reset method allows the user to restore their bank account to some initial starting value in case they become bankrupt while playing.

Your model will also need to set the balance and the ante to reasonable values (such as a starting balance of $200 and a starting ante of $5) when the model is created. It would make sense to use these values when you call -reset, too.

The view: CrapsViewController.xib

With one exception, the particulars of the design of the user interface is up to you. You can add anything extra you would like, but your view must display the following elements:

The controller: CrapsViewController

A reasonable API for your CrapsViewController would look like this, but if you make modifications to the user interface, you are welcome to modify this API to match:
@interface CrapsViewController : UIViewController {
    IBOutlet UILabel* balanceLabel; // displays the current balance
    IBOutlet UILabel* dieLabel1;    // displays the value of die #1
    IBOutlet UILabel* dieLabel2;    // displays the value of die #2
    IBOutlet UILabel* totalLabel;   // displays the sum of the dice
    IBOutlet UILabel* pointLabel;   // displays the value of the point 

    //These two outlets are used for the ante
    IBOutlet UILabel* anteLabel;    // displays the current ante value 
    IBOutlet UISlider* anteSlider;  // allows the controller to move the slider

    CrapsModel* model;              // a pointer to the model
}

- (IBAction)rollPressed;                     // action called when the user presses Roll
- (IBAction)sliderMoved:(UISlider *)sender;  // action called when the user moves the slider

@end

The anteSlider will have a minimum value of 0 and a maximum value of 500 which you will set in InterfaceBuilder. However, if the controller tells the model to set the ante to an illegal value, such as a value above the player's current balance, the model will simply set the ante equal to the something reasonable, in this case the player's current balance. Therefore, your controller will need to read the value of the slider, tell your model to change it's ante, then check with the model to see what the ante actually was set to. If it was set to something different than the user had tried to set it to, your controller will need to move it to the value the model thinks it's at. That's why your anteSlider is both an IBAction (to know when it's moved) and an IBOutlet (to move it if it's set improperly).

You will instantiate your model lazily, following the example from Hangman. You will also want to have a @property for your model, but you don't want that property in your API because then some other object could grab a copy of your model. Instead, you'll want a private property. You make a private property by declaring it in your .m file outside and above the @implementation for the controller as follows:

@interface CrapsViewController()
@property (readonly) CrapsModel* model;
@end

Those parenthesis at the end of the @interface are very important. They tell the compiler that this is an extension to the interface you've already created rather than a new interface. Since this property is declared in the .m file, it won't show up in the API for the class.

The one last piece of the puzzle here is that you'll need make sure that when the program starts, your balance, initial ante, and current point are shown on the screen. In -viewDidLoad, you can make updates to the view that aren't stored in the .xib file, therefore this would be a good place to set those values (based on what the model tells you, not hard coded into the controller).

Sample code

// Returns the unicode string presenting the die face
- (NSString *)dieGlyph:(int)value
{
    NSString *glyph;
    switch (value) {
        case 1: glyph = @"\u2680"; break;
        case 2: glyph = @"\u2681"; break;
        case 3: glyph = @"\u2682"; break;
        case 4: glyph = @"\u2683"; break;
        case 5: glyph = @"\u2684"; break;
        case 6: glyph = @"\u2685"; break;
    }
    return glyph;
}


// Displays an alert with a title and a message
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message
{
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
                                                    message:message
                                                   delegate:self
                                          cancelButtonTitle:@"OK"
                                          otherButtonTitles:nil];
    [alert show];
    [alert release];
}