Source/Game.m
author Jens Alfke <jens@mooseyard.com>
Thu Jul 31 20:01:26 2008 -0700 (2008-07-31)
changeset 24 db8640a38faf
parent 21 2eb229411d73
permissions -rw-r--r--
* Put the spots in the right place in 13x13 and 19x19 boards.
* Fixed some front/back layering issues in Grid.
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@16
    71
- (id) initNewGameWithTable: (GGBLayer*)board
jens@0
    72
{
jens@8
    73
    self = [self init];
jens@10
    74
    if( self ) {
jens@16
    75
        self.table = 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@16
    84
    [_table 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@16
   120
- (GGBLayer*) table
jens@10
   121
{
jens@16
   122
    return _table;
jens@10
   123
}
jens@10
   124
jens@16
   125
- (void) setTable: (GGBLayer*)board
jens@10
   126
{
jens@16
   127
    setObj(&_table,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@16
   132
        [_table 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@22
   146
- (CGFloat) tablePerspectiveAngle
jens@22
   147
{
jens@22
   148
    return _tablePerspectiveAngle;
jens@22
   149
}
jens@22
   150
jens@22
   151
- (void) setTablePerspectiveAngle: (CGFloat)angle
jens@22
   152
{
jens@22
   153
    if( angle != _tablePerspectiveAngle ) {
jens@22
   154
        _tablePerspectiveAngle = angle;
jens@22
   155
        [self perspectiveChanged];
jens@22
   156
    }
jens@22
   157
}
jens@22
   158
jens@22
   159
- (void) perspectiveChanged
jens@22
   160
{
jens@22
   161
}
jens@22
   162
jens@10
   163
jens@10
   164
#pragma mark -
jens@10
   165
#pragma mark PLAYERS:
jens@0
   166
jens@0
   167
jens@0
   168
- (void) setNumberOfPlayers: (unsigned)n
jens@0
   169
{
jens@0
   170
    NSMutableArray *players = [NSMutableArray arrayWithCapacity: n];
jens@0
   171
    for( int i=1; i<=n; i++ ) {
jens@0
   172
        Player *player = [[Player alloc] initWithGame: self];
jens@0
   173
        player.name = [NSString stringWithFormat: @"Player %i",i];
jens@0
   174
        [players addObject: player];
jens@0
   175
        [player release];
jens@0
   176
    }
jens@10
   177
    self.players = players;
jens@0
   178
    self.winner = nil;
jens@10
   179
    
jens@10
   180
    Turn *turn = [[Turn alloc] initStartOfGame: self];
jens@10
   181
    setObj(&_turns, [NSMutableArray arrayWithObject: turn]);
jens@10
   182
    [turn release];
jens@10
   183
    [self _startTurn];
jens@0
   184
}
jens@0
   185
jens@10
   186
- (Player*) remotePlayer
jens@7
   187
{
jens@10
   188
    for( Player *player in _players )
jens@10
   189
        if( ! player.local )
jens@10
   190
            return player;
jens@10
   191
    return nil;
jens@7
   192
}
jens@7
   193
jens@10
   194
- (BOOL) isLocal
jens@7
   195
{
jens@10
   196
    return self.remotePlayer == nil;
jens@7
   197
}
jens@7
   198
jens@10
   199
- (Player*) currentPlayer
jens@10
   200
{
jens@10
   201
    return self.currentTurn.player;
jens@10
   202
}
jens@7
   203
jens@10
   204
+ (NSArray*) keyPathsForValuesAffectingCurrentPlayer {return [NSArray arrayWithObject: @"currentTurn"];}
jens@10
   205
jens@10
   206
jens@10
   207
#pragma mark -
jens@10
   208
#pragma mark TURNS:
jens@10
   209
jens@10
   210
jens@10
   211
- (Turn*) currentTurn
jens@0
   212
{
jens@10
   213
    return [_turns objectAtIndex: _currentTurnNo];
jens@10
   214
}
jens@10
   215
jens@10
   216
- (Turn*) latestTurn
jens@10
   217
{
jens@10
   218
    return [_turns lastObject];
jens@10
   219
}
jens@10
   220
jens@10
   221
+ (NSArray*) keyPathsForValuesAffectingCurrentTurn {return [NSArray arrayWithObject: @"currentTurnNo"];}
jens@10
   222
+ (NSArray*) keyPathsForValuesAffectingLatestTurn  {return [NSArray arrayWithObject: @"turns"];}
jens@10
   223
jens@10
   224
jens@10
   225
- (void) _startTurn
jens@10
   226
{
jens@10
   227
    Turn *lastTurn = [_turns lastObject];
jens@10
   228
    NSAssert(lastTurn.status==kTurnFinished,@"Can't _startTurn till previous turn is finished");
jens@10
   229
    Turn *newTurn = [[Turn alloc] initWithPlayer: lastTurn.nextPlayer];
jens@10
   230
    
jens@10
   231
    [self willChangeValueForKey: @"turns"];
jens@10
   232
    [_turns addObject: newTurn];
jens@10
   233
    [self willChangeValueForKey: @"turns"];
jens@10
   234
    [newTurn release];
jens@10
   235
    self.currentTurnNo = _turns.count-1;
jens@0
   236
}
jens@0
   237
jens@0
   238
jens@15
   239
- (BOOL) okToMove
jens@15
   240
{
jens@15
   241
    Turn *latest = self.latestTurn;
jens@15
   242
    if( latest.player.local && latest.status < kTurnComplete ) {
jens@15
   243
        // Automatically skip from latest finished turn, since board state is the same:
jens@15
   244
        unsigned latestTurnNo = self.maxTurnNo;
jens@15
   245
        if( _currentTurnNo==latestTurnNo-1 ) {
jens@15
   246
            NSLog(@"okToMove: skipping from turn %i to %i",_currentTurnNo,latestTurnNo);
jens@15
   247
            self.currentTurnNo = latestTurnNo;
jens@15
   248
        }
jens@15
   249
        if( _currentTurnNo==latestTurnNo )
jens@15
   250
            return YES;
jens@15
   251
    }
jens@15
   252
    return NO;
jens@15
   253
}
jens@15
   254
jens@15
   255
jens@0
   256
- (void) endTurn
jens@0
   257
{
jens@10
   258
    Turn *curTurn = self.currentTurn;
jens@10
   259
    if( curTurn.isLatestTurn && ! curTurn.replaying ) {
jens@10
   260
        curTurn.status = kTurnComplete;
jens@10
   261
        NSLog(@"--- End of %@", curTurn);
jens@10
   262
        
jens@10
   263
        Player *winner = [self checkForWinner];
jens@10
   264
        if( winner ) {
jens@10
   265
            NSLog(@"*** The %@ Ends! The winner is %@ ! ***", self.class, winner);
jens@10
   266
            self.winner = winner;
jens@10
   267
        }
jens@10
   268
        
jens@10
   269
        if( ! _requireConfirmation || !curTurn.player.local ) 
jens@10
   270
            [self confirmCurrentTurn];
jens@7
   271
jens@10
   272
        [[NSNotificationCenter defaultCenter] postNotificationName: kTurnCompleteNotification
jens@10
   273
                                                            object: curTurn];
jens@7
   274
    }
jens@7
   275
}
jens@7
   276
jens@10
   277
- (void) cancelCurrentTurn
jens@10
   278
{
jens@10
   279
    Turn *curTurn = self.currentTurn;
jens@10
   280
    if( curTurn.status > kTurnEmpty && curTurn.status < kTurnFinished ) {
jens@10
   281
        if( _winner )
jens@10
   282
            self.winner = nil;
jens@16
   283
        if( _table )
jens@10
   284
            self.stateString = curTurn.previousTurn.boardState;
jens@10
   285
        curTurn.status = kTurnEmpty;
jens@10
   286
    }
jens@10
   287
}
jens@10
   288
jens@10
   289
- (void) confirmCurrentTurn
jens@10
   290
{
jens@10
   291
    Turn *curTurn = self.currentTurn;
jens@10
   292
    if( curTurn.status == kTurnComplete ) {
jens@10
   293
        curTurn.status = kTurnFinished;
jens@10
   294
        if( ! _winner )
jens@10
   295
            [self _startTurn];
jens@10
   296
    }
jens@10
   297
}
jens@10
   298
jens@7
   299
jens@7
   300
- (BOOL) isLatestTurn
jens@7
   301
{
jens@10
   302
    return _currentTurnNo == _turns.count-1;
jens@7
   303
}
jens@7
   304
jens@10
   305
- (unsigned) maxTurnNo
jens@10
   306
{
jens@10
   307
    return _turns.count-1;
jens@10
   308
}
jens@7
   309
jens@10
   310
+ (NSArray*) keyPathsForValuesAffectingIsLatestTurn {return [NSArray arrayWithObjects: @"currentTurnNo",@"turns",nil];}
jens@10
   311
+ (NSArray*) keyPathsForValuesAffectingMaxTurnNo    {return [NSArray arrayWithObjects: @"turns",nil];}
jens@10
   312
jens@10
   313
- (unsigned) currentTurnNo
jens@10
   314
{
jens@10
   315
    return _currentTurnNo;
jens@10
   316
}
jens@10
   317
jens@10
   318
jens@10
   319
#pragma mark -
jens@10
   320
#pragma mark REPLAYING TURNS:
jens@10
   321
jens@10
   322
jens@10
   323
- (void) setCurrentTurnNo: (unsigned)turnNo
jens@10
   324
{
jens@10
   325
    NSParameterAssert(turnNo<=self.maxTurnNo);
jens@10
   326
    unsigned oldTurnNo = _currentTurnNo;
jens@10
   327
    if( turnNo != oldTurnNo ) {
jens@16
   328
        if( _table ) {
jens@10
   329
            Turn *turn = [_turns objectAtIndex: turnNo];
jens@10
   330
            NSString *state;
jens@10
   331
            if( turn.status == kTurnEmpty )
jens@10
   332
                state = turn.previousTurn.boardState;
jens@10
   333
            else
jens@10
   334
                state = turn.boardState;
jens@10
   335
            NSAssert1(state,@"empty boardState at turn #%i",turnNo);
jens@10
   336
            _currentTurnNo = turnNo;
jens@10
   337
            if( turnNo==oldTurnNo+1 ) {
jens@10
   338
                NSString *move = turn.move;
jens@10
   339
                if( move ) {
jens@10
   340
                    NSLog(@"Reapplying move '%@'",move);
jens@10
   341
                    turn.replaying = YES;
jens@10
   342
                    @try{
jens@10
   343
                        if( ! [self applyMoveString: move] ) {
jens@10
   344
                            _currentTurnNo = oldTurnNo;
jens@21
   345
                            Warn(@"%@ failed to apply stored move '%@'!", self,move);
jens@10
   346
                            return;
jens@10
   347
                        }
jens@10
   348
                    }@finally{
jens@10
   349
                        turn.replaying = NO;
jens@10
   350
                    }
jens@10
   351
                }
jens@10
   352
            } else {
jens@10
   353
                NSLog(@"Reapplying state '%@'",state);
jens@10
   354
                BeginDisableAnimations();
jens@10
   355
                self.stateString = state;
jens@10
   356
                EndDisableAnimations();
jens@10
   357
            }
jens@10
   358
            if( ! [self.stateString isEqual: state] ) {
jens@10
   359
                _currentTurnNo = oldTurnNo;
jens@21
   360
                Warn(@"%@ failed to apply stored state '%@'!", self,state);
jens@10
   361
                return;
jens@10
   362
            }
jens@10
   363
        } else
jens@10
   364
            _currentTurnNo = turnNo;
jens@10
   365
    }
jens@10
   366
}
jens@10
   367
jens@10
   368
jens@10
   369
- (BOOL) animateMoveFrom: (CALayer<BitHolder>*)src to: (CALayer<BitHolder>*)dst
jens@7
   370
{
jens@7
   371
    if( src==nil || dst==nil || dst==src )
jens@7
   372
        return NO;
jens@7
   373
    Bit *bit = [src canDragBit: src.bit];
jens@7
   374
    if( ! bit || ! [dst canDropBit: bit atPoint: GetCGRectCenter(dst.bounds)]
jens@7
   375
              || ! [self canBit: bit moveFrom: src to: dst] )
jens@7
   376
        return NO;
jens@7
   377
    
jens@16
   378
    ChangeSuperlayer(bit, _table.superlayer, -1);
jens@7
   379
    bit.pickedUp = YES;
jens@7
   380
    dst.highlighted = YES;
jens@7
   381
    [bit performSelector: @selector(setPickedUp:) withObject:nil afterDelay: 0.15];
jens@7
   382
    CGPoint endPosition = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: bit.superlayer];
jens@7
   383
    [bit animateAndBlock: @"position"
jens@8
   384
#if TARGET_OS_IPHONE
jens@8
   385
                    from: [NSValue valueWithCGPoint: bit.position]
jens@8
   386
                      to: [NSValue valueWithCGPoint: endPosition]
jens@8
   387
#else
jens@7
   388
                    from: [NSValue valueWithPoint: NSPointFromCGPoint(bit.position)]
jens@7
   389
                      to: [NSValue valueWithPoint: NSPointFromCGPoint(endPosition)]
jens@8
   390
#endif
jens@7
   391
                duration: 0.25];
jens@7
   392
    dst.bit = bit;
jens@7
   393
    dst.highlighted = NO;
jens@7
   394
    bit.pickedUp = NO;
jens@7
   395
    
jens@7
   396
    [src draggedBit: bit to: dst];
jens@7
   397
    [self bit: bit movedFrom: src to: dst];
jens@10
   398
    return YES;
jens@10
   399
}
jens@10
   400
jens@10
   401
jens@10
   402
- (BOOL) animatePlacementIn: (CALayer<BitHolder>*)dst
jens@10
   403
{
jens@10
   404
    if( dst == nil )
jens@10
   405
        return NO;
jens@10
   406
    Bit *bit = [self bitToPlaceInHolder: dst];
jens@10
   407
    if( ! bit )
jens@10
   408
        return NO;
jens@10
   409
    
jens@10
   410
    CALayer<BitHolder>* oldHolder = (CALayer<BitHolder>*) bit.holder;
jens@10
   411
    if( oldHolder ) {
jens@10
   412
        if( oldHolder != dst ) 
jens@10
   413
            return [self animateMoveFrom: oldHolder to: dst];
jens@10
   414
    } else
jens@16
   415
        bit.position = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: _table.superlayer];
jens@16
   416
    ChangeSuperlayer(bit, _table.superlayer, -1);
jens@10
   417
    bit.pickedUp = YES;
jens@10
   418
    dst.highlighted = YES;
jens@10
   419
    
jens@10
   420
    DelayFor(0.2);
jens@10
   421
    
jens@10
   422
    dst.bit = bit;
jens@10
   423
    dst.highlighted = NO;
jens@10
   424
    bit.pickedUp = NO;
jens@10
   425
    
jens@10
   426
    [self bit: bit movedFrom: nil to: dst];
jens@7
   427
    return YES;
jens@7
   428
}
jens@7
   429
     
jens@7
   430
jens@0
   431
#pragma mark -
jens@0
   432
#pragma mark GAMEPLAY METHODS TO BE OVERRIDDEN:
jens@0
   433
jens@0
   434
jens@10
   435
+ (NSString*) identifier
jens@10
   436
{
jens@10
   437
    NSString* name = [self description];
jens@10
   438
    if( [name hasSuffix: @"Game"] )
jens@10
   439
        name = [name substringToIndex: name.length-4];
jens@10
   440
    return name;
jens@10
   441
}
jens@10
   442
jens@10
   443
+ (NSString*) displayName
jens@10
   444
{
jens@10
   445
    return [self identifier];
jens@10
   446
}
jens@10
   447
jens@4
   448
+ (BOOL) landscapeOriented
jens@4
   449
{
jens@4
   450
    return NO;
jens@4
   451
}
jens@4
   452
jens@4
   453
jens@10
   454
- (NSString*) initialStateString
jens@10
   455
{
jens@10
   456
    return @"";
jens@10
   457
}
jens@10
   458
jens@10
   459
jens@10
   460
- (CGImageRef) iconForPlayer: (int)playerIndex
jens@10
   461
{
jens@10
   462
    return nil;
jens@10
   463
}
jens@10
   464
jens@10
   465
jens@0
   466
- (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src
jens@0
   467
{
jens@0
   468
    return YES;
jens@0
   469
}
jens@0
   470
jens@0
   471
- (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
jens@0
   472
{
jens@0
   473
    return YES;
jens@0
   474
}
jens@0
   475
jens@0
   476
- (void) bit: (Bit*)bit movedFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
jens@0
   477
{
jens@0
   478
    [self endTurn];
jens@0
   479
}
jens@0
   480
jens@3
   481
- (Bit*) bitToPlaceInHolder: (id<BitHolder>)holder
jens@3
   482
{
jens@3
   483
    return nil;
jens@3
   484
}
jens@3
   485
jens@3
   486
jens@0
   487
- (BOOL) clickedBit: (Bit*)bit
jens@0
   488
{
jens@0
   489
    return YES;
jens@0
   490
}
jens@0
   491
jens@0
   492
- (Player*) checkForWinner
jens@0
   493
{
jens@0
   494
    return nil;
jens@0
   495
}
jens@0
   496
jens@10
   497
/* These are abstract
jens@10
   498
 
jens@7
   499
- (NSString*) stateString                   {return @"";}
jens@7
   500
- (void) setStateString: (NSString*)s       { }
jens@7
   501
jens@7
   502
- (BOOL) applyMoveString: (NSString*)move   {return NO;}
jens@10
   503
*/
jens@7
   504
jens@0
   505
@end
jens@0
   506
jens@0
   507
jens@0
   508
jens@0
   509
jens@10
   510
#pragma mark -
jens@0
   511
@implementation CALayer (Game)
jens@0
   512
jens@0
   513
- (Game*) game
jens@0
   514
{
jens@0
   515
    // The Game object stores a pointer to itself as the value of the "Game" property
jens@0
   516
    // of its root layer. (CALayers can have arbitrary KV properties stored into them.)
jens@0
   517
    for( CALayer *layer = self; layer; layer=layer.superlayer ) {
jens@0
   518
        Game *game = [layer valueForKey: @"Game"];
jens@0
   519
        if( game )
jens@0
   520
            return game;
jens@0
   521
    }
jens@0
   522
    NSAssert1(NO,@"Couldn't look up Game from %@",self);
jens@0
   523
    return nil;
jens@0
   524
}
jens@0
   525
jens@0
   526
@end