Source/Game.m
author Jens Alfke <jens@mooseyard.com>
Sat Jul 05 17:46:43 2008 -0700 (2008-07-05)
changeset 11 436cbdf56810
parent 9 a59acc683080
child 14 4585c74d809c
permissions -rw-r--r--
* Improved drag-and-drop (supports CandyBar)
* Fixed DiscPiece
* Inheritable layer styles
etc.
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@10
   128
    // Store a pointer to myself as the value of the "Game" property
jens@10
   129
    // of my root layer. (CALayers can have arbitrary KV properties stored into them.)
jens@10
   130
    // This is used by the -[CALayer game] category method defined below, to find the Game.
jens@10
   131
    [_board setValue: self forKey: @"Game"];
jens@10
   132
    
jens@10
   133
    BeginDisableAnimations();
jens@10
   134
    
jens@10
   135
    // Tell the game to add the necessary bits to the board:
jens@10
   136
    [self setUpBoard];
jens@10
   137
    
jens@10
   138
    // Re-apply the current state to set up the pieces/cards:
jens@10
   139
    self.stateString = [[_turns objectAtIndex: _currentTurnNo] boardState];
jens@10
   140
    
jens@10
   141
    EndDisableAnimations();
jens@10
   142
}
jens@10
   143
jens@10
   144
jens@10
   145
#pragma mark -
jens@10
   146
#pragma mark PLAYERS:
jens@0
   147
jens@0
   148
jens@0
   149
- (void) setNumberOfPlayers: (unsigned)n
jens@0
   150
{
jens@0
   151
    NSMutableArray *players = [NSMutableArray arrayWithCapacity: n];
jens@0
   152
    for( int i=1; i<=n; i++ ) {
jens@0
   153
        Player *player = [[Player alloc] initWithGame: self];
jens@0
   154
        player.name = [NSString stringWithFormat: @"Player %i",i];
jens@0
   155
        [players addObject: player];
jens@0
   156
        [player release];
jens@0
   157
    }
jens@10
   158
    self.players = players;
jens@0
   159
    self.winner = nil;
jens@10
   160
    
jens@10
   161
    Turn *turn = [[Turn alloc] initStartOfGame: self];
jens@10
   162
    setObj(&_turns, [NSMutableArray arrayWithObject: turn]);
jens@10
   163
    [turn release];
jens@10
   164
    [self _startTurn];
jens@0
   165
}
jens@0
   166
jens@10
   167
- (Player*) remotePlayer
jens@7
   168
{
jens@10
   169
    for( Player *player in _players )
jens@10
   170
        if( ! player.local )
jens@10
   171
            return player;
jens@10
   172
    return nil;
jens@7
   173
}
jens@7
   174
jens@10
   175
- (BOOL) isLocal
jens@7
   176
{
jens@10
   177
    return self.remotePlayer == nil;
jens@7
   178
}
jens@7
   179
jens@10
   180
- (Player*) currentPlayer
jens@10
   181
{
jens@10
   182
    return self.currentTurn.player;
jens@10
   183
}
jens@7
   184
jens@10
   185
+ (NSArray*) keyPathsForValuesAffectingCurrentPlayer {return [NSArray arrayWithObject: @"currentTurn"];}
jens@10
   186
jens@10
   187
jens@10
   188
#pragma mark -
jens@10
   189
#pragma mark TURNS:
jens@10
   190
jens@10
   191
jens@10
   192
- (Turn*) currentTurn
jens@0
   193
{
jens@10
   194
    return [_turns objectAtIndex: _currentTurnNo];
jens@10
   195
}
jens@10
   196
jens@10
   197
- (Turn*) latestTurn
jens@10
   198
{
jens@10
   199
    return [_turns lastObject];
jens@10
   200
}
jens@10
   201
jens@10
   202
+ (NSArray*) keyPathsForValuesAffectingCurrentTurn {return [NSArray arrayWithObject: @"currentTurnNo"];}
jens@10
   203
+ (NSArray*) keyPathsForValuesAffectingLatestTurn  {return [NSArray arrayWithObject: @"turns"];}
jens@10
   204
jens@10
   205
jens@10
   206
- (void) _startTurn
jens@10
   207
{
jens@10
   208
    Turn *lastTurn = [_turns lastObject];
jens@10
   209
    NSAssert(lastTurn.status==kTurnFinished,@"Can't _startTurn till previous turn is finished");
jens@10
   210
    Turn *newTurn = [[Turn alloc] initWithPlayer: lastTurn.nextPlayer];
jens@10
   211
    
jens@10
   212
    [self willChangeValueForKey: @"turns"];
jens@10
   213
    [_turns addObject: newTurn];
jens@10
   214
    [self willChangeValueForKey: @"turns"];
jens@10
   215
    [newTurn release];
jens@10
   216
    self.currentTurnNo = _turns.count-1;
jens@0
   217
}
jens@0
   218
jens@0
   219
jens@0
   220
- (void) endTurn
jens@0
   221
{
jens@10
   222
    Turn *curTurn = self.currentTurn;
jens@10
   223
    if( curTurn.isLatestTurn && ! curTurn.replaying ) {
jens@10
   224
        curTurn.status = kTurnComplete;
jens@10
   225
        NSLog(@"--- End of %@", curTurn);
jens@10
   226
        
jens@10
   227
        Player *winner = [self checkForWinner];
jens@10
   228
        if( winner ) {
jens@10
   229
            NSLog(@"*** The %@ Ends! The winner is %@ ! ***", self.class, winner);
jens@10
   230
            self.winner = winner;
jens@10
   231
        }
jens@10
   232
        
jens@10
   233
        if( ! _requireConfirmation || !curTurn.player.local ) 
jens@10
   234
            [self confirmCurrentTurn];
jens@7
   235
jens@10
   236
        [[NSNotificationCenter defaultCenter] postNotificationName: kTurnCompleteNotification
jens@10
   237
                                                            object: curTurn];
jens@7
   238
    }
jens@7
   239
}
jens@7
   240
jens@10
   241
- (void) cancelCurrentTurn
jens@10
   242
{
jens@10
   243
    Turn *curTurn = self.currentTurn;
jens@10
   244
    if( curTurn.status > kTurnEmpty && curTurn.status < kTurnFinished ) {
jens@10
   245
        if( _winner )
jens@10
   246
            self.winner = nil;
jens@10
   247
        if( _board )
jens@10
   248
            self.stateString = curTurn.previousTurn.boardState;
jens@10
   249
        curTurn.status = kTurnEmpty;
jens@10
   250
    }
jens@10
   251
}
jens@10
   252
jens@10
   253
- (void) confirmCurrentTurn
jens@10
   254
{
jens@10
   255
    Turn *curTurn = self.currentTurn;
jens@10
   256
    if( curTurn.status == kTurnComplete ) {
jens@10
   257
        curTurn.status = kTurnFinished;
jens@10
   258
        if( ! _winner )
jens@10
   259
            [self _startTurn];
jens@10
   260
    }
jens@10
   261
}
jens@10
   262
jens@7
   263
jens@7
   264
- (BOOL) isLatestTurn
jens@7
   265
{
jens@10
   266
    return _currentTurnNo == _turns.count-1;
jens@7
   267
}
jens@7
   268
jens@10
   269
- (unsigned) maxTurnNo
jens@10
   270
{
jens@10
   271
    return _turns.count-1;
jens@10
   272
}
jens@7
   273
jens@10
   274
+ (NSArray*) keyPathsForValuesAffectingIsLatestTurn {return [NSArray arrayWithObjects: @"currentTurnNo",@"turns",nil];}
jens@10
   275
+ (NSArray*) keyPathsForValuesAffectingMaxTurnNo    {return [NSArray arrayWithObjects: @"turns",nil];}
jens@10
   276
jens@10
   277
- (unsigned) currentTurnNo
jens@10
   278
{
jens@10
   279
    return _currentTurnNo;
jens@10
   280
}
jens@10
   281
jens@10
   282
jens@10
   283
#pragma mark -
jens@10
   284
#pragma mark REPLAYING TURNS:
jens@10
   285
jens@10
   286
jens@10
   287
- (void) setCurrentTurnNo: (unsigned)turnNo
jens@10
   288
{
jens@10
   289
    NSParameterAssert(turnNo<=self.maxTurnNo);
jens@10
   290
    unsigned oldTurnNo = _currentTurnNo;
jens@10
   291
    if( turnNo != oldTurnNo ) {
jens@10
   292
        if( _board ) {
jens@10
   293
            Turn *turn = [_turns objectAtIndex: turnNo];
jens@10
   294
            NSString *state;
jens@10
   295
            if( turn.status == kTurnEmpty )
jens@10
   296
                state = turn.previousTurn.boardState;
jens@10
   297
            else
jens@10
   298
                state = turn.boardState;
jens@10
   299
            NSAssert1(state,@"empty boardState at turn #%i",turnNo);
jens@10
   300
            _currentTurnNo = turnNo;
jens@10
   301
            if( turnNo==oldTurnNo+1 ) {
jens@10
   302
                NSString *move = turn.move;
jens@10
   303
                if( move ) {
jens@10
   304
                    NSLog(@"Reapplying move '%@'",move);
jens@10
   305
                    turn.replaying = YES;
jens@10
   306
                    @try{
jens@10
   307
                        if( ! [self applyMoveString: move] ) {
jens@10
   308
                            _currentTurnNo = oldTurnNo;
jens@10
   309
                            NSBeep();
jens@10
   310
                            NSLog(@"WARNING: %@ failed to apply stored move '%@'!", self,move);
jens@10
   311
                            return;
jens@10
   312
                        }
jens@10
   313
                    }@finally{
jens@10
   314
                        turn.replaying = NO;
jens@10
   315
                    }
jens@10
   316
                }
jens@10
   317
            } else {
jens@10
   318
                NSLog(@"Reapplying state '%@'",state);
jens@10
   319
                BeginDisableAnimations();
jens@10
   320
                self.stateString = state;
jens@10
   321
                EndDisableAnimations();
jens@10
   322
            }
jens@10
   323
            if( ! [self.stateString isEqual: state] ) {
jens@10
   324
                _currentTurnNo = oldTurnNo;
jens@10
   325
                NSBeep();
jens@10
   326
                NSLog(@"WARNING: %@ failed to apply stored state '%@'!", self,state);
jens@10
   327
                return;
jens@10
   328
            }
jens@10
   329
        } else
jens@10
   330
            _currentTurnNo = turnNo;
jens@10
   331
    }
jens@10
   332
}
jens@10
   333
jens@10
   334
jens@10
   335
- (BOOL) animateMoveFrom: (CALayer<BitHolder>*)src to: (CALayer<BitHolder>*)dst
jens@7
   336
{
jens@7
   337
    if( src==nil || dst==nil || dst==src )
jens@7
   338
        return NO;
jens@7
   339
    Bit *bit = [src canDragBit: src.bit];
jens@7
   340
    if( ! bit || ! [dst canDropBit: bit atPoint: GetCGRectCenter(dst.bounds)]
jens@7
   341
              || ! [self canBit: bit moveFrom: src to: dst] )
jens@7
   342
        return NO;
jens@7
   343
    
jens@7
   344
    ChangeSuperlayer(bit, _board.superlayer, -1);
jens@7
   345
    bit.pickedUp = YES;
jens@7
   346
    dst.highlighted = YES;
jens@7
   347
    [bit performSelector: @selector(setPickedUp:) withObject:nil afterDelay: 0.15];
jens@7
   348
    CGPoint endPosition = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: bit.superlayer];
jens@7
   349
    [bit animateAndBlock: @"position"
jens@8
   350
#if TARGET_OS_IPHONE
jens@8
   351
                    from: [NSValue valueWithCGPoint: bit.position]
jens@8
   352
                      to: [NSValue valueWithCGPoint: endPosition]
jens@8
   353
#else
jens@7
   354
                    from: [NSValue valueWithPoint: NSPointFromCGPoint(bit.position)]
jens@7
   355
                      to: [NSValue valueWithPoint: NSPointFromCGPoint(endPosition)]
jens@8
   356
#endif
jens@7
   357
                duration: 0.25];
jens@7
   358
    dst.bit = bit;
jens@7
   359
    dst.highlighted = NO;
jens@7
   360
    bit.pickedUp = NO;
jens@7
   361
    
jens@7
   362
    [src draggedBit: bit to: dst];
jens@7
   363
    [self bit: bit movedFrom: src to: dst];
jens@10
   364
    return YES;
jens@10
   365
}
jens@10
   366
jens@10
   367
jens@10
   368
- (BOOL) animatePlacementIn: (CALayer<BitHolder>*)dst
jens@10
   369
{
jens@10
   370
    if( dst == nil )
jens@10
   371
        return NO;
jens@10
   372
    Bit *bit = [self bitToPlaceInHolder: dst];
jens@10
   373
    if( ! bit )
jens@10
   374
        return NO;
jens@10
   375
    
jens@10
   376
    CALayer<BitHolder>* oldHolder = (CALayer<BitHolder>*) bit.holder;
jens@10
   377
    if( oldHolder ) {
jens@10
   378
        if( oldHolder != dst ) 
jens@10
   379
            return [self animateMoveFrom: oldHolder to: dst];
jens@10
   380
    } else
jens@10
   381
        bit.position = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: _board.superlayer];
jens@10
   382
    ChangeSuperlayer(bit, _board.superlayer, -1);
jens@10
   383
    bit.pickedUp = YES;
jens@10
   384
    dst.highlighted = YES;
jens@10
   385
    
jens@10
   386
    DelayFor(0.2);
jens@10
   387
    
jens@10
   388
    dst.bit = bit;
jens@10
   389
    dst.highlighted = NO;
jens@10
   390
    bit.pickedUp = NO;
jens@10
   391
    
jens@10
   392
    [self bit: bit movedFrom: nil to: dst];
jens@7
   393
    return YES;
jens@7
   394
}
jens@7
   395
     
jens@7
   396
jens@0
   397
#pragma mark -
jens@0
   398
#pragma mark GAMEPLAY METHODS TO BE OVERRIDDEN:
jens@0
   399
jens@0
   400
jens@10
   401
+ (NSString*) identifier
jens@10
   402
{
jens@10
   403
    NSString* name = [self description];
jens@10
   404
    if( [name hasSuffix: @"Game"] )
jens@10
   405
        name = [name substringToIndex: name.length-4];
jens@10
   406
    return name;
jens@10
   407
}
jens@10
   408
jens@10
   409
+ (NSString*) displayName
jens@10
   410
{
jens@10
   411
    return [self identifier];
jens@10
   412
}
jens@10
   413
jens@4
   414
+ (BOOL) landscapeOriented
jens@4
   415
{
jens@4
   416
    return NO;
jens@4
   417
}
jens@4
   418
jens@4
   419
jens@10
   420
- (NSString*) initialStateString
jens@10
   421
{
jens@10
   422
    return @"";
jens@10
   423
}
jens@10
   424
jens@10
   425
jens@10
   426
- (CGImageRef) iconForPlayer: (int)playerIndex
jens@10
   427
{
jens@10
   428
    return nil;
jens@10
   429
}
jens@10
   430
jens@10
   431
jens@0
   432
- (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src
jens@0
   433
{
jens@0
   434
    return YES;
jens@0
   435
}
jens@0
   436
jens@0
   437
- (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
jens@0
   438
{
jens@0
   439
    return YES;
jens@0
   440
}
jens@0
   441
jens@0
   442
- (void) bit: (Bit*)bit movedFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
jens@0
   443
{
jens@0
   444
    [self endTurn];
jens@0
   445
}
jens@0
   446
jens@3
   447
- (Bit*) bitToPlaceInHolder: (id<BitHolder>)holder
jens@3
   448
{
jens@3
   449
    return nil;
jens@3
   450
}
jens@3
   451
jens@3
   452
jens@0
   453
- (BOOL) clickedBit: (Bit*)bit
jens@0
   454
{
jens@0
   455
    return YES;
jens@0
   456
}
jens@0
   457
jens@0
   458
- (Player*) checkForWinner
jens@0
   459
{
jens@0
   460
    return nil;
jens@0
   461
}
jens@0
   462
jens@10
   463
/* These are abstract
jens@10
   464
 
jens@7
   465
- (NSString*) stateString                   {return @"";}
jens@7
   466
- (void) setStateString: (NSString*)s       { }
jens@7
   467
jens@7
   468
- (BOOL) applyMoveString: (NSString*)move   {return NO;}
jens@10
   469
*/
jens@7
   470
jens@0
   471
@end
jens@0
   472
jens@0
   473
jens@0
   474
jens@0
   475
jens@10
   476
#pragma mark -
jens@0
   477
@implementation CALayer (Game)
jens@0
   478
jens@0
   479
- (Game*) game
jens@0
   480
{
jens@0
   481
    // The Game object stores a pointer to itself as the value of the "Game" property
jens@0
   482
    // of its root layer. (CALayers can have arbitrary KV properties stored into them.)
jens@0
   483
    for( CALayer *layer = self; layer; layer=layer.superlayer ) {
jens@0
   484
        Game *game = [layer valueForKey: @"Game"];
jens@0
   485
        if( game )
jens@0
   486
            return game;
jens@0
   487
    }
jens@0
   488
    NSAssert1(NO,@"Couldn't look up Game from %@",self);
jens@0
   489
    return nil;
jens@0
   490
}
jens@0
   491
jens@0
   492
@end