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