Source/Game.m
author Jens Alfke <jens@mooseyard.com>
Tue Jul 08 20:32:52 2008 -0700 (2008-07-08)
changeset 14 4585c74d809c
parent 10 6c78cc6bd7a6
child 15 73f8c889f053
permissions -rw-r--r--
Minor fixes.
jens@0
     1
/*  This code is based on Apple's "GeekGameBoard" sample code, version 1.0.
jens@0
     2
    http://developer.apple.com/samplecode/GeekGameBoard/
jens@0
     3
    Copyright © 2007 Apple Inc. Copyright © 2008 Jens Alfke. All Rights Reserved.
jens@0
     4
jens@0
     5
    Redistribution and use in source and binary forms, with or without modification, are permitted
jens@0
     6
    provided that the following conditions are met:
jens@0
     7
jens@0
     8
    * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@0
     9
      and the following disclaimer.
jens@0
    10
    * Redistributions in binary form must reproduce the above copyright notice, this list of
jens@0
    11
      conditions and the following disclaimer in the documentation and/or other materials provided
jens@0
    12
      with the distribution.
jens@0
    13
jens@0
    14
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@0
    15
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
jens@0
    16
    FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@0
    17
    BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@0
    18
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
jens@0
    19
    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
jens@0
    20
    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
jens@0
    21
    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@0
    22
*/
jens@10
    23
#import "Game+Protected.h"
jens@7
    24
#import "QuartzUtils.h"
jens@10
    25
#import "GGBUtils.h"
jens@0
    26
jens@0
    27
jens@0
    28
@interface Game ()
jens@0
    29
@property (copy) NSArray *players;
jens@10
    30
@property (assign) Player *winner;
jens@10
    31
- (void) _startTurn;
jens@0
    32
@end
jens@0
    33
jens@0
    34
jens@0
    35
@implementation Game
jens@0
    36
jens@0
    37
jens@10
    38
- (id) init
jens@0
    39
{
jens@8
    40
    self = [super init];
jens@8
    41
    if (self != nil) {
jens@10
    42
        // Don't create _turns till -initWithCoder or -setNumberOfPlayers:.
jens@8
    43
    }
jens@8
    44
    return self;
jens@8
    45
}
jens@8
    46
jens@10
    47
jens@10
    48
- (id) initWithCoder: (NSCoder*)decoder
jens@8
    49
{
jens@10
    50
    self = [self init];
jens@10
    51
    if( self ) {
jens@10
    52
        _players = [[decoder decodeObjectForKey: @"players"] mutableCopy];
jens@10
    53
        _winner =   [decoder decodeObjectForKey: @"winner"];
jens@10
    54
        _turns   = [[decoder decodeObjectForKey: @"turns"] mutableCopy];
jens@10
    55
        _extraValues = [[decoder decodeObjectForKey: @"extraValues"] mutableCopy];
jens@10
    56
        self.currentTurnNo = self.maxTurnNo;
jens@10
    57
    }
jens@8
    58
    return self;
jens@8
    59
}
jens@8
    60
jens@10
    61
jens@10
    62
- (void) encodeWithCoder: (NSCoder*)coder
jens@10
    63
{
jens@10
    64
    [coder encodeObject: _players forKey: @"players"];
jens@10
    65
    [coder encodeObject: _winner forKey: @"winner"];
jens@10
    66
    [coder encodeObject: _turns   forKey: @"turns"];
jens@10
    67
    [coder encodeObject: _extraValues forKey: @"extraValues"];
jens@10
    68
}
jens@10
    69
jens@10
    70
jens@10
    71
- (id) initNewGameWithBoard: (GGBLayer*)board
jens@0
    72
{
jens@8
    73
    self = [self init];
jens@10
    74
    if( self ) {
jens@10
    75
        self.board = board;
jens@10
    76
        NSAssert1(_players && _turns, @"%@ failed to set numberOfPlayers",self);
jens@0
    77
    }
jens@0
    78
    return self;
jens@0
    79
}
jens@0
    80
jens@0
    81
jens@0
    82
- (void) dealloc
jens@0
    83
{
jens@0
    84
    [_board release];
jens@0
    85
    [_players release];
jens@10
    86
    [_turns release];
jens@10
    87
    [_extraValues release];
jens@0
    88
    [super dealloc];
jens@0
    89
}
jens@0
    90
jens@0
    91
jens@10
    92
@synthesize players=_players, winner=_winner, turns=_turns, requireConfirmation=_requireConfirmation;
jens@10
    93
jens@10
    94
jens@10
    95
- (id)valueForUndefinedKey:(NSString *)key
jens@10
    96
{
jens@10
    97
    return [_extraValues objectForKey: key];
jens@10
    98
}
jens@10
    99
jens@10
   100
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
jens@10
   101
{
jens@10
   102
    if( ! _extraValues )
jens@10
   103
        _extraValues = [[NSMutableDictionary alloc] init];
jens@10
   104
    if( value )
jens@10
   105
        [_extraValues setObject: value forKey: key];
jens@10
   106
    else
jens@10
   107
        [_extraValues removeObjectForKey: key];
jens@10
   108
}
jens@10
   109
jens@10
   110
jens@10
   111
#pragma mark -
jens@10
   112
#pragma mark BOARD:
jens@10
   113
jens@10
   114
jens@10
   115
- (void) setUpBoard
jens@10
   116
{
jens@10
   117
    NSAssert1(NO,@"%@ forgot to implement -setUpBoard",[self class]);
jens@10
   118
}
jens@10
   119
jens@10
   120
- (GGBLayer*) board
jens@10
   121
{
jens@10
   122
    return _board;
jens@10
   123
}
jens@10
   124
jens@10
   125
- (void) setBoard: (GGBLayer*)board
jens@10
   126
{
jens@10
   127
    setObj(&_board,board);
jens@14
   128
    if( board ) {
jens@14
   129
        // Store a pointer to myself as the value of the "Game" property
jens@14
   130
        // of my root layer. (CALayers can have arbitrary KV properties stored into them.)
jens@14
   131
        // This is used by the -[CALayer game] category method defined below, to find the Game.
jens@14
   132
        [_board setValue: self forKey: @"Game"];
jens@14
   133
        
jens@14
   134
        BeginDisableAnimations();
jens@14
   135
        
jens@14
   136
        // Tell the game to add the necessary bits to the board:
jens@14
   137
        [self setUpBoard];
jens@14
   138
        
jens@14
   139
        // Re-apply the current state to set up the pieces/cards:
jens@14
   140
        self.stateString = [[_turns objectAtIndex: _currentTurnNo] boardState];
jens@14
   141
        
jens@14
   142
        EndDisableAnimations();
jens@14
   143
    }
jens@10
   144
}
jens@10
   145
jens@10
   146
jens@10
   147
#pragma mark -
jens@10
   148
#pragma mark PLAYERS:
jens@0
   149
jens@0
   150
jens@0
   151
- (void) setNumberOfPlayers: (unsigned)n
jens@0
   152
{
jens@0
   153
    NSMutableArray *players = [NSMutableArray arrayWithCapacity: n];
jens@0
   154
    for( int i=1; i<=n; i++ ) {
jens@0
   155
        Player *player = [[Player alloc] initWithGame: self];
jens@0
   156
        player.name = [NSString stringWithFormat: @"Player %i",i];
jens@0
   157
        [players addObject: player];
jens@0
   158
        [player release];
jens@0
   159
    }
jens@10
   160
    self.players = players;
jens@0
   161
    self.winner = nil;
jens@10
   162
    
jens@10
   163
    Turn *turn = [[Turn alloc] initStartOfGame: self];
jens@10
   164
    setObj(&_turns, [NSMutableArray arrayWithObject: turn]);
jens@10
   165
    [turn release];
jens@10
   166
    [self _startTurn];
jens@0
   167
}
jens@0
   168
jens@10
   169
- (Player*) remotePlayer
jens@7
   170
{
jens@10
   171
    for( Player *player in _players )
jens@10
   172
        if( ! player.local )
jens@10
   173
            return player;
jens@10
   174
    return nil;
jens@7
   175
}
jens@7
   176
jens@10
   177
- (BOOL) isLocal
jens@7
   178
{
jens@10
   179
    return self.remotePlayer == nil;
jens@7
   180
}
jens@7
   181
jens@10
   182
- (Player*) currentPlayer
jens@10
   183
{
jens@10
   184
    return self.currentTurn.player;
jens@10
   185
}
jens@7
   186
jens@10
   187
+ (NSArray*) keyPathsForValuesAffectingCurrentPlayer {return [NSArray arrayWithObject: @"currentTurn"];}
jens@10
   188
jens@10
   189
jens@10
   190
#pragma mark -
jens@10
   191
#pragma mark TURNS:
jens@10
   192
jens@10
   193
jens@10
   194
- (Turn*) currentTurn
jens@0
   195
{
jens@10
   196
    return [_turns objectAtIndex: _currentTurnNo];
jens@10
   197
}
jens@10
   198
jens@10
   199
- (Turn*) latestTurn
jens@10
   200
{
jens@10
   201
    return [_turns lastObject];
jens@10
   202
}
jens@10
   203
jens@10
   204
+ (NSArray*) keyPathsForValuesAffectingCurrentTurn {return [NSArray arrayWithObject: @"currentTurnNo"];}
jens@10
   205
+ (NSArray*) keyPathsForValuesAffectingLatestTurn  {return [NSArray arrayWithObject: @"turns"];}
jens@10
   206
jens@10
   207
jens@10
   208
- (void) _startTurn
jens@10
   209
{
jens@10
   210
    Turn *lastTurn = [_turns lastObject];
jens@10
   211
    NSAssert(lastTurn.status==kTurnFinished,@"Can't _startTurn till previous turn is finished");
jens@10
   212
    Turn *newTurn = [[Turn alloc] initWithPlayer: lastTurn.nextPlayer];
jens@10
   213
    
jens@10
   214
    [self willChangeValueForKey: @"turns"];
jens@10
   215
    [_turns addObject: newTurn];
jens@10
   216
    [self willChangeValueForKey: @"turns"];
jens@10
   217
    [newTurn release];
jens@10
   218
    self.currentTurnNo = _turns.count-1;
jens@0
   219
}
jens@0
   220
jens@0
   221
jens@0
   222
- (void) endTurn
jens@0
   223
{
jens@10
   224
    Turn *curTurn = self.currentTurn;
jens@10
   225
    if( curTurn.isLatestTurn && ! curTurn.replaying ) {
jens@10
   226
        curTurn.status = kTurnComplete;
jens@10
   227
        NSLog(@"--- End of %@", curTurn);
jens@10
   228
        
jens@10
   229
        Player *winner = [self checkForWinner];
jens@10
   230
        if( winner ) {
jens@10
   231
            NSLog(@"*** The %@ Ends! The winner is %@ ! ***", self.class, winner);
jens@10
   232
            self.winner = winner;
jens@10
   233
        }
jens@10
   234
        
jens@10
   235
        if( ! _requireConfirmation || !curTurn.player.local ) 
jens@10
   236
            [self confirmCurrentTurn];
jens@7
   237
jens@10
   238
        [[NSNotificationCenter defaultCenter] postNotificationName: kTurnCompleteNotification
jens@10
   239
                                                            object: curTurn];
jens@7
   240
    }
jens@7
   241
}
jens@7
   242
jens@10
   243
- (void) cancelCurrentTurn
jens@10
   244
{
jens@10
   245
    Turn *curTurn = self.currentTurn;
jens@10
   246
    if( curTurn.status > kTurnEmpty && curTurn.status < kTurnFinished ) {
jens@10
   247
        if( _winner )
jens@10
   248
            self.winner = nil;
jens@10
   249
        if( _board )
jens@10
   250
            self.stateString = curTurn.previousTurn.boardState;
jens@10
   251
        curTurn.status = kTurnEmpty;
jens@10
   252
    }
jens@10
   253
}
jens@10
   254
jens@10
   255
- (void) confirmCurrentTurn
jens@10
   256
{
jens@10
   257
    Turn *curTurn = self.currentTurn;
jens@10
   258
    if( curTurn.status == kTurnComplete ) {
jens@10
   259
        curTurn.status = kTurnFinished;
jens@10
   260
        if( ! _winner )
jens@10
   261
            [self _startTurn];
jens@10
   262
    }
jens@10
   263
}
jens@10
   264
jens@7
   265
jens@7
   266
- (BOOL) isLatestTurn
jens@7
   267
{
jens@10
   268
    return _currentTurnNo == _turns.count-1;
jens@7
   269
}
jens@7
   270
jens@10
   271
- (unsigned) maxTurnNo
jens@10
   272
{
jens@10
   273
    return _turns.count-1;
jens@10
   274
}
jens@7
   275
jens@10
   276
+ (NSArray*) keyPathsForValuesAffectingIsLatestTurn {return [NSArray arrayWithObjects: @"currentTurnNo",@"turns",nil];}
jens@10
   277
+ (NSArray*) keyPathsForValuesAffectingMaxTurnNo    {return [NSArray arrayWithObjects: @"turns",nil];}
jens@10
   278
jens@10
   279
- (unsigned) currentTurnNo
jens@10
   280
{
jens@10
   281
    return _currentTurnNo;
jens@10
   282
}
jens@10
   283
jens@10
   284
jens@10
   285
#pragma mark -
jens@10
   286
#pragma mark REPLAYING TURNS:
jens@10
   287
jens@10
   288
jens@10
   289
- (void) setCurrentTurnNo: (unsigned)turnNo
jens@10
   290
{
jens@10
   291
    NSParameterAssert(turnNo<=self.maxTurnNo);
jens@10
   292
    unsigned oldTurnNo = _currentTurnNo;
jens@10
   293
    if( turnNo != oldTurnNo ) {
jens@10
   294
        if( _board ) {
jens@10
   295
            Turn *turn = [_turns objectAtIndex: turnNo];
jens@10
   296
            NSString *state;
jens@10
   297
            if( turn.status == kTurnEmpty )
jens@10
   298
                state = turn.previousTurn.boardState;
jens@10
   299
            else
jens@10
   300
                state = turn.boardState;
jens@10
   301
            NSAssert1(state,@"empty boardState at turn #%i",turnNo);
jens@10
   302
            _currentTurnNo = turnNo;
jens@10
   303
            if( turnNo==oldTurnNo+1 ) {
jens@10
   304
                NSString *move = turn.move;
jens@10
   305
                if( move ) {
jens@10
   306
                    NSLog(@"Reapplying move '%@'",move);
jens@10
   307
                    turn.replaying = YES;
jens@10
   308
                    @try{
jens@10
   309
                        if( ! [self applyMoveString: move] ) {
jens@10
   310
                            _currentTurnNo = oldTurnNo;
jens@10
   311
                            NSBeep();
jens@10
   312
                            NSLog(@"WARNING: %@ failed to apply stored move '%@'!", self,move);
jens@10
   313
                            return;
jens@10
   314
                        }
jens@10
   315
                    }@finally{
jens@10
   316
                        turn.replaying = NO;
jens@10
   317
                    }
jens@10
   318
                }
jens@10
   319
            } else {
jens@10
   320
                NSLog(@"Reapplying state '%@'",state);
jens@10
   321
                BeginDisableAnimations();
jens@10
   322
                self.stateString = state;
jens@10
   323
                EndDisableAnimations();
jens@10
   324
            }
jens@10
   325
            if( ! [self.stateString isEqual: state] ) {
jens@10
   326
                _currentTurnNo = oldTurnNo;
jens@10
   327
                NSBeep();
jens@10
   328
                NSLog(@"WARNING: %@ failed to apply stored state '%@'!", self,state);
jens@10
   329
                return;
jens@10
   330
            }
jens@10
   331
        } else
jens@10
   332
            _currentTurnNo = turnNo;
jens@10
   333
    }
jens@10
   334
}
jens@10
   335
jens@10
   336
jens@10
   337
- (BOOL) animateMoveFrom: (CALayer<BitHolder>*)src to: (CALayer<BitHolder>*)dst
jens@7
   338
{
jens@7
   339
    if( src==nil || dst==nil || dst==src )
jens@7
   340
        return NO;
jens@7
   341
    Bit *bit = [src canDragBit: src.bit];
jens@7
   342
    if( ! bit || ! [dst canDropBit: bit atPoint: GetCGRectCenter(dst.bounds)]
jens@7
   343
              || ! [self canBit: bit moveFrom: src to: dst] )
jens@7
   344
        return NO;
jens@7
   345
    
jens@7
   346
    ChangeSuperlayer(bit, _board.superlayer, -1);
jens@7
   347
    bit.pickedUp = YES;
jens@7
   348
    dst.highlighted = YES;
jens@7
   349
    [bit performSelector: @selector(setPickedUp:) withObject:nil afterDelay: 0.15];
jens@7
   350
    CGPoint endPosition = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: bit.superlayer];
jens@7
   351
    [bit animateAndBlock: @"position"
jens@8
   352
#if TARGET_OS_IPHONE
jens@8
   353
                    from: [NSValue valueWithCGPoint: bit.position]
jens@8
   354
                      to: [NSValue valueWithCGPoint: endPosition]
jens@8
   355
#else
jens@7
   356
                    from: [NSValue valueWithPoint: NSPointFromCGPoint(bit.position)]
jens@7
   357
                      to: [NSValue valueWithPoint: NSPointFromCGPoint(endPosition)]
jens@8
   358
#endif
jens@7
   359
                duration: 0.25];
jens@7
   360
    dst.bit = bit;
jens@7
   361
    dst.highlighted = NO;
jens@7
   362
    bit.pickedUp = NO;
jens@7
   363
    
jens@7
   364
    [src draggedBit: bit to: dst];
jens@7
   365
    [self bit: bit movedFrom: src to: dst];
jens@10
   366
    return YES;
jens@10
   367
}
jens@10
   368
jens@10
   369
jens@10
   370
- (BOOL) animatePlacementIn: (CALayer<BitHolder>*)dst
jens@10
   371
{
jens@10
   372
    if( dst == nil )
jens@10
   373
        return NO;
jens@10
   374
    Bit *bit = [self bitToPlaceInHolder: dst];
jens@10
   375
    if( ! bit )
jens@10
   376
        return NO;
jens@10
   377
    
jens@10
   378
    CALayer<BitHolder>* oldHolder = (CALayer<BitHolder>*) bit.holder;
jens@10
   379
    if( oldHolder ) {
jens@10
   380
        if( oldHolder != dst ) 
jens@10
   381
            return [self animateMoveFrom: oldHolder to: dst];
jens@10
   382
    } else
jens@10
   383
        bit.position = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: _board.superlayer];
jens@10
   384
    ChangeSuperlayer(bit, _board.superlayer, -1);
jens@10
   385
    bit.pickedUp = YES;
jens@10
   386
    dst.highlighted = YES;
jens@10
   387
    
jens@10
   388
    DelayFor(0.2);
jens@10
   389
    
jens@10
   390
    dst.bit = bit;
jens@10
   391
    dst.highlighted = NO;
jens@10
   392
    bit.pickedUp = NO;
jens@10
   393
    
jens@10
   394
    [self bit: bit movedFrom: nil to: dst];
jens@7
   395
    return YES;
jens@7
   396
}
jens@7
   397
     
jens@7
   398
jens@0
   399
#pragma mark -
jens@0
   400
#pragma mark GAMEPLAY METHODS TO BE OVERRIDDEN:
jens@0
   401
jens@0
   402
jens@10
   403
+ (NSString*) identifier
jens@10
   404
{
jens@10
   405
    NSString* name = [self description];
jens@10
   406
    if( [name hasSuffix: @"Game"] )
jens@10
   407
        name = [name substringToIndex: name.length-4];
jens@10
   408
    return name;
jens@10
   409
}
jens@10
   410
jens@10
   411
+ (NSString*) displayName
jens@10
   412
{
jens@10
   413
    return [self identifier];
jens@10
   414
}
jens@10
   415
jens@4
   416
+ (BOOL) landscapeOriented
jens@4
   417
{
jens@4
   418
    return NO;
jens@4
   419
}
jens@4
   420
jens@4
   421
jens@10
   422
- (NSString*) initialStateString
jens@10
   423
{
jens@10
   424
    return @"";
jens@10
   425
}
jens@10
   426
jens@10
   427
jens@10
   428
- (CGImageRef) iconForPlayer: (int)playerIndex
jens@10
   429
{
jens@10
   430
    return nil;
jens@10
   431
}
jens@10
   432
jens@10
   433
jens@0
   434
- (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src
jens@0
   435
{
jens@0
   436
    return YES;
jens@0
   437
}
jens@0
   438
jens@0
   439
- (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
jens@0
   440
{
jens@0
   441
    return YES;
jens@0
   442
}
jens@0
   443
jens@0
   444
- (void) bit: (Bit*)bit movedFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
jens@0
   445
{
jens@0
   446
    [self endTurn];
jens@0
   447
}
jens@0
   448
jens@3
   449
- (Bit*) bitToPlaceInHolder: (id<BitHolder>)holder
jens@3
   450
{
jens@3
   451
    return nil;
jens@3
   452
}
jens@3
   453
jens@3
   454
jens@0
   455
- (BOOL) clickedBit: (Bit*)bit
jens@0
   456
{
jens@0
   457
    return YES;
jens@0
   458
}
jens@0
   459
jens@0
   460
- (Player*) checkForWinner
jens@0
   461
{
jens@0
   462
    return nil;
jens@0
   463
}
jens@0
   464
jens@10
   465
/* These are abstract
jens@10
   466
 
jens@7
   467
- (NSString*) stateString                   {return @"";}
jens@7
   468
- (void) setStateString: (NSString*)s       { }
jens@7
   469
jens@7
   470
- (BOOL) applyMoveString: (NSString*)move   {return NO;}
jens@10
   471
*/
jens@7
   472
jens@0
   473
@end
jens@0
   474
jens@0
   475
jens@0
   476
jens@0
   477
jens@10
   478
#pragma mark -
jens@0
   479
@implementation CALayer (Game)
jens@0
   480
jens@0
   481
- (Game*) game
jens@0
   482
{
jens@0
   483
    // The Game object stores a pointer to itself as the value of the "Game" property
jens@0
   484
    // of its root layer. (CALayers can have arbitrary KV properties stored into them.)
jens@0
   485
    for( CALayer *layer = self; layer; layer=layer.superlayer ) {
jens@0
   486
        Game *game = [layer valueForKey: @"Game"];
jens@0
   487
        if( game )
jens@0
   488
            return game;
jens@0
   489
    }
jens@0
   490
    NSAssert1(NO,@"Couldn't look up Game from %@",self);
jens@0
   491
    return nil;
jens@0
   492
}
jens@0
   493
jens@0
   494
@end