cs193p – Assignment #4 Extra Task #3

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

Make the Set game a two player game. There’s no need to go overboard here. Think of a simple UI and a straightforward implementation that make sense.

In storyboard dublicate the set-game view controller, add a now image for the tab bar and remove the cheat button (we did not add a penalty for cheating in task #2 and it would be unfair for the two player game πŸ˜‰ ) – and of course link it to the tab view contorller:

cs193p – assignment #4 extra task #3 – two-player view controller
cs193p – assignment #4 extra task #3 – two-player view controller


Because we do not want to break anything for the previous task (that’s also why we added a new tab) create a new sub class of the set-game-view-controller class and link the just created (copied/duplicated) view controller in storyboard to this new class.

The new class uses two properties – an array, which holds the different scores of the players, and an integer to indicate which player is currently playing (… and don’t forget to instantiate the array lazily):

@property (strong, nonatomic) NSMutableArray *playerScores; // of NSNumber
@property (nonatomic) NSUInteger currentPlayer;
...
- (NSMutableArray *)playerScores
{
    if (!_playerScores) {
        _playerScores = [NSMutableArray arrayWithArray:@[@0, @0]];
    }
    return _playerScores;
}

To reset the values when dealing a new deck override the deal-button method:

- (void)touchDealButton:(UIButton *)sender
{
    self.playerScores = nil;
    self.currentPlayer = 0;
    [super touchDealButton:sender];
}

The main magic of the two player game is handled by the following method. The game model holds the total score, thus it is necessary to calculated any changed score by subtracting the other players score. Let’s say a player does not loose his turn as long he does not flip up a mismatching combination of cards, or he adds new cards to the game. Thus if the new score plus flipping costs is less then the previous score a player looses his turn (which actually assumes that the flipping cost is less than a mismatch penalty). In addition check if the parent class has set the score adjustment property (which it does when a player adds new cards). … and to make the calculation easier, the two player game does not have a penalty for add new cards:

- (void)calculateMultiPlayerScore
{
    NSUInteger otherPlayer = (self.currentPlayer + 1) % 2;
    NSInteger newScore = self.game.score - [self.playerScores[otherPlayer] integerValue];
    if ((newScore + self.gameSettings.flipCost < [self.playerScores[self.currentPlayer] integerValue])
        || self.scoreAdjustment) {
        self.playerScores[self.currentPlayer] = @(newScore);
        self.currentPlayer = otherPlayer;
        self.scoreAdjustment = 0;
    } else {
        self.playerScores[self.currentPlayer] = @(newScore);
    }
}

After the user interface has been updated by the parent class, rewrite the score label using attributed strings (coloring the current players score):

- (void)updateUI
{
    [super updateUI];
    [self calculateMultiPlayerScore];
    
    NSString *text = [NSString stringWithFormat:@"P1: %@  P2: %@",
                      self.playerScores[0], self.playerScores[1]];
    NSMutableAttributedString *label = [[NSMutableAttributedString alloc] initWithString:text];
    NSRange range = ]];
    if (range.location != NSNotFound) {
        [label addAttributes:@{ NSStrokeWidthAttributeName : @-3,NSForegroundColorAttributeName : [UIColor blueColor] }
                       range:range];
    }    
    self.scoreLabel.attributedText = label;
}

… that’s it we did not touch any old code … but because we used a couple of properties of the super class which have been private before, we need to move them into the header file to make them public … also the action method for the deal button needs to be public:

@property (nonatomic, strong) CardMatchingGame *game;
@property (strong, nonatomic) GameSettings *gameSettings;
@property (nonatomic) NSInteger scoreAdjustment;
@property (weak, nonatomic) IBOutlet UILabel *scoreLabel;

- (IBAction)touchDealButton:(UIButton *)sender;

The complete code is available on github.

FacebooktwitterredditpinterestlinkedintumblrmailFacebooktwitterredditpinterestlinkedintumblrmail

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

  1. In your method, players change turns when the current player looses his turn?

    If (current player loose his turn) {

    self.currentPlayer = otherPlayer;
    ….
    }

  2. Just for showing an alternative I’ll post my implementation of this. I did also subclass SetCardGameViewController, and then:

    Add two properties to CardMatchingGame.h:
    @property (nonatomic, readonly) NSMutableArray *playerScores;
    @property (nonatomic, readonly) NSUInteger currentPlayer;
    and a method:
    -(void)switchPlayer;

    And change the initialiser to be able to add the number of players:
    -(instancetype)initWithCardCount:(NSUInteger)count usingDeck:(Deck *)deck numberOfPlayers:(NSUInteger)numberOfPlayers;

    CardMatchingGame.m:
    @property (nonatomic, readwrite) NSInteger lastScore;
    @property (nonatomic, strong) NSMutableArray *lastChosenCards;
    @property (nonatomic) NSUInteger numberOfPlayers;

    Method implementations:
    -(void)switchPlayer {
    if (self.currentPlayer == self.numberOfPlayers-1) {
    self.currentPlayer = 0;
    } else { self.currentPlayer++; }
    }

    -(NSUInteger)currentPlayer {
    if (_currentPlayer < self.numberOfPlayers) {
    return _currentPlayer;
    }
    return NSNotFound;
    }

    -(NSMutableArray *)playerScores {
    if (!_playerScores) {
    _playerScores = [[NSMutableArray alloc] init];
    for (int i = 0; i < self.numberOfPlayers; i++) {
    [_playerScores addObject:@0];
    }
    }
    return _playerScores;
    }

    -(void)setScore:(NSInteger)score {
    self.playerScores[self.currentPlayer] = [NSNumber numberWithInteger:score];
    }
    -(NSInteger)score {
    NSNumber *numScore;
    id score = self.playerScores[self.currentPlayer];
    if ([score isKindOfClass:[NSNumber class]]) {
    numScore = (NSNumber *)score;
    }
    return [numScore integerValue];
    }

    -(instancetype)initWithCardCount:(NSUInteger)count usingDeck:(Deck *)deck numberOfPlayers:(NSUInteger)numberOfPlayers{
    self = [super init];
    if (self) {
    […]
    self.numberOfPlayers = numberOfPlayers;
    }

    return self;
    }

Leave a Reply

Your email address will not be published.