cs193p – Assignment #3 Extra Task #4

Please note, this blog entry is from a previous course. You might want to check out the current one.

Add yet another tab for some “settings” in the game (match bonuses, etc.).

Create new model handling the game settings (like we did already for game results). Its public interface consists of our game settings:

@property (nonatomic) int matchBonus;
@property (nonatomic) int mismatchPenalty;
@property (nonatomic) int flipCost;


The getters for those properties access a helper method which accesses the user defaults and if no valid setting is there returns a default value:

- (int)intValueForKey:(NSString *)key withDefault:(int)defaultValue
{
    NSDictionary *settings = [[NSUserDefaults standardUserDefaults] 
        dictionaryForKey:GAME_SETTINGS_KEY];
    if (!settings) return defaultValue;
    if (![[settings allKeys] containsObject:key]) return defaultValue;
    return [settings[key] intValue];
}

- (int)matchBonus
{
    return [self intValueForKey:MATCHBONUS_KEY withDefault:4];
}

- (int)mismatchPenalty
{
    return [self intValueForKey:MISMATCHPENALTY_KEY withDefault:2];
}

- (int)flipCost
{
    return [self intValueForKey:FLIPCOST_KEY withDefault:1];
}

The setters of the settings properties use another helper method to save their values to the user defaults. (In addition the helper method creates a new property list (dictionary) if it has not been initialized, yet):

- (void)setIntValue:(int)value forKey:(NSString *)key
{
    NSMutableDictionary *settings = [[[NSUserDefaults standardUserDefaults] 
        dictionaryForKey:GAME_SETTINGS_KEY] mutableCopy];
    if (!settings) {
        settings = [[NSMutableDictionary alloc] init];
    }
    settings[key] = @(value);
    [[NSUserDefaults standardUserDefaults] setObject:settings
                                              forKey:GAME_SETTINGS_KEY];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

- (void)setMatchBonus:(int)matchBonus
{
    [self setIntValue:matchBonus forKey:MATCHBONUS_KEY];
}

- (void)setMismatchPenalty:(int)mismatchPenalty
{
    [self setIntValue:mismatchPenalty forKey:MISMATCHPENALTY_KEY];
}

- (void)setFlipCost:(int)flipCost
{
    [self setIntValue:flipCost forKey:FLIPCOST_KEY];
}

Also note, to code above uses constants heavily 😉

#define GAME_SETTINGS_KEY @"Game_Settings_Key"
#define MATCHBONUS_KEY @"MatchBonus_Key"
#define MISMATCHPENALTY_KEY @"MismatchPenalty_Key"
#define FLIPCOST_KEY @"FlipCost_Key"
#define NUMBERPLAYINGCARDS_KEY @"NumberPlayingCards_Key"

Though the card-matching-game model uses those game settings, to be consistent with the MVC scheme, it should not access another model directly. Add properties to its public interface:

@property (nonatomic) int matchBonus;
@property (nonatomic) int mismatchPenalty;
@property (nonatomic) int flipCost;

… and use them instead of the currently used constants.

- (void)chooseCardAtIndex:(NSUInteger)index
{
                ...
                    self.lastScore = matchScore * self.matchBonus;
                ...
                    self.lastScore = - self.mismatchPenalty;
            ....
            self.score += self.lastScore - self.flipCost;
    ...
}

In addition you could set default values when initializing the game:

- (instancetype)initWithCardCount:(NSUInteger)count
                        usingDeck:(Deck *)deck
{
            ...
            _matchBonus = MATCH_BONUS;
            _mismatchPenalty = MISMATCH_PENALTY;
            _flipCost = COST_TO_CHOOSE;
    ...
}

The generic game view controller will now use the settings model and store it in a property, initialized lazily:

#import "GameSettings.h"
...
@property (strong, nonatomic) GameSettings *gameSettings;
...
- (GameSettings *)gameSettings
{
    if (!_gameSettings) _gameSettings = [[GameSettings alloc] init];
    return _gameSettings;
}

… and then pass the settings to the game when it is initialized:

- (CardMatchingGame *)game
{
        ...
        _game.matchBonus = self.gameSettings.matchBonus;
        _game.mismatchPenalty = self.gameSettings.mismatchPenalty;
        _game.flipCost = self.gameSettings.flipCost;
    ...
}

… and also when the view will appear on screen (this way the settings from the settings tab are used immediately when returning to the card game):

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    self.game.matchBonus = self.gameSettings.matchBonus;
    self.game.mismatchPenalty = self.gameSettings.mismatchPenalty;
    self.game.flipCost = self.gameSettings.flipCost;
}

Add a new view controller to the storyboard, link it to the tab-bar controller, name it and choose an icon you setup in your image assets. Add three sliders for the three settings, three labels for their name and another three labels for the value chosen by the sliders:

cs193p – assignment #3 extra task #4 – settings tab
cs193p – assignment #3 extra task #4 – settings tab

Create a new view-controller class and connect it to the new view controller in the story board.

Create outlets for the value labels and the sliders:

@property (weak, nonatomic) IBOutlet UILabel *matchBonusLabel;
@property (weak, nonatomic) IBOutlet UILabel *mismatchPenaltyLabel;
@property (weak, nonatomic) IBOutlet UILabel *flipCostLabel;
@property (weak, nonatomic) IBOutlet UISlider *matchBonusSlider;
@property (weak, nonatomic) IBOutlet UISlider *mismatchPenaltySlider;
@property (weak, nonatomic) IBOutlet UISlider *flipCostSlider;

… and another property to hold the settings model, and initialize it lazily:

import "GameSettings.h"
...
@property (strong, nonatomic) GameSettings *gameSettings;
...
- (GameSettings *)gameSettings
{
    if (!_gameSettings) _gameSettings = [[GameSettings alloc] init];
    return _gameSettings;
}

Add three actions for the sliders which set the labels as well as the settings model (using a helper method which also takes care that only discrete values are selectable):

- (void)setLabel:(UILabel *)label forSlider:(UISlider *)slider
{
    int sliderValue;
    sliderValue = lroundf(slider.value);
    [slider setValue:sliderValue animated:NO];    
    label.text = [NSString stringWithFormat:@"%d", sliderValue];
}

- (IBAction)matchBonusSliderChanged:(UISlider *)sender {
    [self setLabel:self.matchBonusLabel forSlider:sender];
    self.gameSettings.matchBonus = floor(sender.value);
}

- (IBAction)mismatchPenaltySliderChanged:(UISlider *)sender {
    [self setLabel:self.mismatchPenaltyLabel forSlider:sender];
    self.gameSettings.mismatchPenalty = floor(sender.value);
}

- (IBAction)flipCostSliderChanged:(UISlider *)sender {
    [self setLabel:self.flipCostLabel forSlider:sender];
    self.gameSettings.flipCost = floor(sender.value);
}

When the tab is selected, set the labels and sliders according to the stored settings:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    self.matchBonusSlider.value = self.gameSettings.matchBonus;
    self.mismatchPenaltySlider.value = self.gameSettings.mismatchPenalty;
    self.flipCostSlider.value = self.gameSettings.flipCost;
    [self setLabel:self.matchBonusLabel forSlider:self.matchBonusSlider];
    [self setLabel:self.mismatchPenaltyLabel forSlider:self.mismatchPenaltySlider];
    [self setLabel:self.flipCostLabel forSlider:self.flipCostSlider];
}

Finally, create new launch images and adjust them in your image settings.

The complete code is available on github.

Facebooktwittergoogle_plusredditpinterestlinkedintumblrmailFacebooktwittergoogle_plusredditpinterestlinkedintumblrmail

Flattr this!

8 thoughts on “cs193p – Assignment #3 Extra Task #4”

  1. Great code once again. I only had one problem: on execution the values of the dictionary Game_Settings_Key within NSUserDefaults were all set to 1. Could be I’ve made a typo. Once you’ve changed the maximum value of the sliders and then adjusted the slider the code works nicely. I was wondering: on using the getter of matchBonus you’re returning the default value, but it is not set within the dictionary. The changing of the dictionary is done with the setMatchBonus. Are you sure this setter is being called upon if you just play the game (without changing the sliders)? At what point would the dictionary been set to the standard value of 4 for instance when not changing the sliders? I could be wrong hence the question.

    1. When there are no NSDefaults, or it does not contain a previously stored value for the key, the default (4) is used. Because it is the default value, and I already know it, I do not store it in the NSDefaults. Next time the getter gets called, it is not stored, and will use the “default” value 4 …

      1. Yes, I saw this in code. However on running it the sliders stood all at 1 (also at the end of the slider). Then I performed an NSLog of the dictionary in NSUserDefaults and all values stood at 1. I even went copy pasting your code over the written code at the hand of your example. It kept standing at 1 (in NSUserDefaults). This is logical as they didn’t store the defaultValue in NSUserDefaults. However, the sliders stood also at 1, so not at 4. The label showed 1. Then I increased the maximum value of the sliders at 4, the sliders jumped at 1. Upon changing their value (by sliding) NSUserDefaults was adjusted and I no longer had any problems.

        1. … sorry, to give you the least possible helpful answer one could give: It works for me 😉

          I removed, the app form the simulator to make sure that there are no NSDefaults … rerun the app … and it worked … I even changed the default value from the code, and it showed …

          Could you post your code on github and provide the link?

  2. I’m not an expert, but why not make this a static class? it feels so unnecessary to instantiate GameSettings.
    I created no properties but only class methods (matchBonus, setMatchBonus:). What’s your idea about this?

    And another question:
    You wrote “Though the card-matching-game model uses those game settings, to be consistent with the MVC scheme, it should not access another model directly. Add properties to its public interface:”.
    So, maybe I missed this in the lecture on MVC, but is that like a core principle of MVC, that e.g. CardMatchingGame class cannot touch the GameSettings class?

    Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *