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