Source/Game.m
author Jens Alfke <jens@mooseyard.com>
Fri Jul 18 13:26:59 2008 -0700 (2008-07-18)
changeset 20 7c9ecb09a612
parent 15 73f8c889f053
child 21 2eb229411d73
permissions -rw-r--r--
Checkers and Hexchequer now detect victory when the other player can't move.
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@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@15
   222
- (BOOL) okToMove
jens@15
   223
{
jens@15
   224
    Turn *latest = self.latestTurn;
jens@15
   225
    if( latest.player.local && latest.status < kTurnComplete ) {
jens@15
   226
        // Automatically skip from latest finished turn, since board state is the same:
jens@15
   227
        unsigned latestTurnNo = self.maxTurnNo;
jens@15
   228
        if( _currentTurnNo==latestTurnNo-1 ) {
jens@15
   229
            NSLog(@"okToMove: skipping from turn %i to %i",_currentTurnNo,latestTurnNo);
jens@15
   230
            self.currentTurnNo = latestTurnNo;
jens@15
   231
        }
jens@15
   232
        if( _currentTurnNo==latestTurnNo )
jens@15
   233
            return YES;
jens@15
   234
    }
jens@15
   235
    return NO;
jens@15
   236
}
jens@15
   237
jens@15
   238
jens@0
   239
- (void) endTurn
jens@0
   240
{
jens@10
   241
    Turn *curTurn = self.currentTurn;
jens@10
   242
    if( curTurn.isLatestTurn && ! curTurn.replaying ) {
jens@10
   243
        curTurn.status = kTurnComplete;
jens@10
   244
        NSLog(@"--- End of %@", curTurn);
jens@10
   245
        
jens@10
   246
        Player *winner = [self checkForWinner];
jens@10
   247
        if( winner ) {
jens@10
   248
            NSLog(@"*** The %@ Ends! The winner is %@ ! ***", self.class, winner);
jens@10
   249
            self.winner = winner;
jens@10
   250
        }
jens@10
   251
        
jens@10
   252
        if( ! _requireConfirmation || !curTurn.player.local ) 
jens@10
   253
            [self confirmCurrentTurn];
jens@7
   254
jens@10
   255
        [[NSNotificationCenter defaultCenter] postNotificationName: kTurnCompleteNotification
jens@10
   256
                                                            object: curTurn];
jens@7
   257
    }
jens@7
   258
}
jens@7
   259
jens@10
   260
- (void) cancelCurrentTurn
jens@10
   261
{
jens@10
   262
    Turn *curTurn = self.currentTurn;
jens@10
   263
    if( curTurn.status > kTurnEmpty && curTurn.status < kTurnFinished ) {
jens@10
   264
        if( _winner )
jens@10
   265
            self.winner = nil;
jens@16
   266
        if( _table )
jens@10
   267
            self.stateString = curTurn.previousTurn.boardState;
jens@10
   268
        curTurn.status = kTurnEmpty;
jens@10
   269
    }
jens@10
   270
}
jens@10
   271
jens@10
   272
- (void) confirmCurrentTurn
jens@10
   273
{
jens@10
   274
    Turn *curTurn = self.currentTurn;
jens@10
   275
    if( curTurn.status == kTurnComplete ) {
jens@10
   276
        curTurn.status = kTurnFinished;
jens@10
   277
        if( ! _winner )
jens@10
   278
            [self _startTurn];
jens@10
   279
    }
jens@10
   280
}
jens@10
   281
jens@7
   282
jens@7
   283
- (BOOL) isLatestTurn
jens@7
   284
{
jens@10
   285
    return _currentTurnNo == _turns.count-1;
jens@7
   286
}
jens@7
   287
jens@10
   288
- (unsigned) maxTurnNo
jens@10
   289
{
jens@10
   290
    return _turns.count-1;
jens@10
   291
}
jens@7
   292
jens@10
   293
+ (NSArray*) keyPathsForValuesAffectingIsLatestTurn {return [NSArray arrayWithObjects: @"currentTurnNo",@"turns",nil];}
jens@10
   294
+ (NSArray*) keyPathsForValuesAffectingMaxTurnNo    {return [NSArray arrayWithObjects: @"turns",nil];}
jens@10
   295
jens@10
   296
- (unsigned) currentTurnNo
jens@10
   297
{
jens@10
   298
    return _currentTurnNo;
jens@10
   299
}
jens@10
   300
jens@10
   301
jens@10
   302
#pragma mark -
jens@10
   303
#pragma mark REPLAYING TURNS:
jens@10
   304
jens@10
   305
jens@10
   306
- (void) setCurrentTurnNo: (unsigned)turnNo
jens@10
   307
{
jens@10
   308
    NSParameterAssert(turnNo<=self.maxTurnNo);
jens@10
   309
    unsigned oldTurnNo = _currentTurnNo;
jens@10
   310
    if( turnNo != oldTurnNo ) {
jens@16
   311
        if( _table ) {
jens@10
   312
            Turn *turn = [_turns objectAtIndex: turnNo];
jens@10
   313
            NSString *state;
jens@10
   314
            if( turn.status == kTurnEmpty )
jens@10
   315
                state = turn.previousTurn.boardState;
jens@10
   316
            else
jens@10
   317
                state = turn.boardState;
jens@10
   318
            NSAssert1(state,@"empty boardState at turn #%i",turnNo);
jens@10
   319
            _currentTurnNo = turnNo;
jens@10
   320
            if( turnNo==oldTurnNo+1 ) {
jens@10
   321
                NSString *move = turn.move;
jens@10
   322
                if( move ) {
jens@10
   323
                    NSLog(@"Reapplying move '%@'",move);
jens@10
   324
                    turn.replaying = YES;
jens@10
   325
                    @try{
jens@10
   326
                        if( ! [self applyMoveString: move] ) {
jens@10
   327
                            _currentTurnNo = oldTurnNo;
jens@10
   328
                            NSLog(@"WARNING: %@ failed to apply stored move '%@'!", self,move);
jens@10
   329
                            return;
jens@10
   330
                        }
jens@10
   331
                    }@finally{
jens@10
   332
                        turn.replaying = NO;
jens@10
   333
                    }
jens@10
   334
                }
jens@10
   335
            } else {
jens@10
   336
                NSLog(@"Reapplying state '%@'",state);
jens@10
   337
                BeginDisableAnimations();
jens@10
   338
                self.stateString = state;
jens@10
   339
                EndDisableAnimations();
jens@10
   340
            }
jens@10
   341
            if( ! [self.stateString isEqual: state] ) {
jens@10
   342
                _currentTurnNo = oldTurnNo;
jens@10
   343
                NSLog(@"WARNING: %@ failed to apply stored state '%@'!", self,state);
jens@10
   344
                return;
jens@10
   345
            }
jens@10
   346
        } else
jens@10
   347
            _currentTurnNo = turnNo;
jens@10
   348
    }
jens@10
   349
}
jens@10
   350
jens@10
   351
jens@10
   352
- (BOOL) animateMoveFrom: (CALayer<BitHolder>*)src to: (CALayer<BitHolder>*)dst
jens@7
   353
{
jens@7
   354
    if( src==nil || dst==nil || dst==src )
jens@7
   355
        return NO;
jens@7
   356
    Bit *bit = [src canDragBit: src.bit];
jens@7
   357
    if( ! bit || ! [dst canDropBit: bit atPoint: GetCGRectCenter(dst.bounds)]
jens@7
   358
              || ! [self canBit: bit moveFrom: src to: dst] )
jens@7
   359
        return NO;
jens@7
   360
    
jens@16
   361
    ChangeSuperlayer(bit, _table.superlayer, -1);
jens@7
   362
    bit.pickedUp = YES;
jens@7
   363
    dst.highlighted = YES;
jens@7
   364
    [bit performSelector: @selector(setPickedUp:) withObject:nil afterDelay: 0.15];
jens@7
   365
    CGPoint endPosition = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: bit.superlayer];
jens@7
   366
    [bit animateAndBlock: @"position"
jens@8
   367
#if TARGET_OS_IPHONE
jens@8
   368
                    from: [NSValue valueWithCGPoint: bit.position]
jens@8
   369
                      to: [NSValue valueWithCGPoint: endPosition]
jens@8
   370
#else
jens@7
   371
                    from: [NSValue valueWithPoint: NSPointFromCGPoint(bit.position)]
jens@7
   372
                      to: [NSValue valueWithPoint: NSPointFromCGPoint(endPosition)]
jens@8
   373
#endif
jens@7
   374
                duration: 0.25];
jens@7
   375
    dst.bit = bit;
jens@7
   376
    dst.highlighted = NO;
jens@7
   377
    bit.pickedUp = NO;
jens@7
   378
    
jens@7
   379
    [src draggedBit: bit to: dst];
jens@7
   380
    [self bit: bit movedFrom: src to: dst];
jens@10
   381
    return YES;
jens@10
   382
}
jens@10
   383
jens@10
   384
jens@10
   385
- (BOOL) animatePlacementIn: (CALayer<BitHolder>*)dst
jens@10
   386
{
jens@10
   387
    if( dst == nil )
jens@10
   388
        return NO;
jens@10
   389
    Bit *bit = [self bitToPlaceInHolder: dst];
jens@10
   390
    if( ! bit )
jens@10
   391
        return NO;
jens@10
   392
    
jens@10
   393
    CALayer<BitHolder>* oldHolder = (CALayer<BitHolder>*) bit.holder;
jens@10
   394
    if( oldHolder ) {
jens@10
   395
        if( oldHolder != dst ) 
jens@10
   396
            return [self animateMoveFrom: oldHolder to: dst];
jens@10
   397
    } else
jens@16
   398
        bit.position = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: _table.superlayer];
jens@16
   399
    ChangeSuperlayer(bit, _table.superlayer, -1);
jens@10
   400
    bit.pickedUp = YES;
jens@10
   401
    dst.highlighted = YES;
jens@10
   402
    
jens@10
   403
    DelayFor(0.2);
jens@10
   404
    
jens@10
   405
    dst.bit = bit;
jens@10
   406
    dst.highlighted = NO;
jens@10
   407
    bit.pickedUp = NO;
jens@10
   408
    
jens@10
   409
    [self bit: bit movedFrom: nil to: dst];
jens@7
   410
    return YES;
jens@7
   411
}
jens@7
   412
     
jens@7
   413
jens@0
   414
#pragma mark -
jens@0
   415
#pragma mark GAMEPLAY METHODS TO BE OVERRIDDEN:
jens@0
   416
jens@0
   417
jens@10
   418
+ (NSString*) identifier
jens@10
   419
{
jens@10
   420
    NSString* name = [self description];
jens@10
   421
    if( [name hasSuffix: @"Game"] )
jens@10
   422
        name = [name substringToIndex: name.length-4];
jens@10
   423
    return name;
jens@10
   424
}
jens@10
   425
jens@10
   426
+ (NSString*) displayName
jens@10
   427
{
jens@10
   428
    return [self identifier];
jens@10
   429
}
jens@10
   430
jens@4
   431
+ (BOOL) landscapeOriented
jens@4
   432
{
jens@4
   433
    return NO;
jens@4
   434
}
jens@4
   435
jens@4
   436
jens@10
   437
- (NSString*) initialStateString
jens@10
   438
{
jens@10
   439
    return @"";
jens@10
   440
}
jens@10
   441
jens@10
   442
jens@10
   443
- (CGImageRef) iconForPlayer: (int)playerIndex
jens@10
   444
{
jens@10
   445
    return nil;
jens@10
   446
}
jens@10
   447
jens@10
   448
jens@0
   449
- (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src
jens@0
   450
{
jens@0
   451
    return YES;
jens@0
   452
}
jens@0
   453
jens@0
   454
- (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
jens@0
   455
{
jens@0
   456
    return YES;
jens@0
   457
}
jens@0
   458
jens@0
   459
- (void) bit: (Bit*)bit movedFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
jens@0
   460
{
jens@0
   461
    [self endTurn];
jens@0
   462
}
jens@0
   463
jens@3
   464
- (Bit*) bitToPlaceInHolder: (id<BitHolder>)holder
jens@3
   465
{
jens@3
   466
    return nil;
jens@3
   467
}
jens@3
   468
jens@3
   469
jens@0
   470
- (BOOL) clickedBit: (Bit*)bit
jens@0
   471
{
jens@0
   472
    return YES;
jens@0
   473
}
jens@0
   474
jens@0
   475
- (Player*) checkForWinner
jens@0
   476
{
jens@0
   477
    return nil;
jens@0
   478
}
jens@0
   479
jens@10
   480
/* These are abstract
jens@10
   481
 
jens@7
   482
- (NSString*) stateString                   {return @"";}
jens@7
   483
- (void) setStateString: (NSString*)s       { }
jens@7
   484
jens@7
   485
- (BOOL) applyMoveString: (NSString*)move   {return NO;}
jens@10
   486
*/
jens@7
   487
jens@0
   488
@end
jens@0
   489
jens@0
   490
jens@0
   491
jens@0
   492
jens@10
   493
#pragma mark -
jens@0
   494
@implementation CALayer (Game)
jens@0
   495
jens@0
   496
- (Game*) game
jens@0
   497
{
jens@0
   498
    // The Game object stores a pointer to itself as the value of the "Game" property
jens@0
   499
    // of its root layer. (CALayers can have arbitrary KV properties stored into them.)
jens@0
   500
    for( CALayer *layer = self; layer; layer=layer.superlayer ) {
jens@0
   501
        Game *game = [layer valueForKey: @"Game"];
jens@0
   502
        if( game )
jens@0
   503
            return game;
jens@0
   504
    }
jens@0
   505
    NSAssert1(NO,@"Couldn't look up Game from %@",self);
jens@0
   506
    return nil;
jens@0
   507
}
jens@0
   508
jens@0
   509
@end