Source/Game.m
author Jens Alfke <jens@mooseyard.com>
Wed Jul 09 17:07:45 2008 -0700 (2008-07-09)
changeset 15 73f8c889f053
parent 14 4585c74d809c
child 16 28392c9a969f
permissions -rw-r--r--
More tweaks, including a "reversed" property for Grids to show the second player's perspective without turning the pieces upside-down.
     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) initNewGameWithBoard: (GGBLayer*)board
    72 {
    73     self = [self init];
    74     if( self ) {
    75         self.board = board;
    76         NSAssert1(_players && _turns, @"%@ failed to set numberOfPlayers",self);
    77     }
    78     return self;
    79 }
    80 
    81 
    82 - (void) dealloc
    83 {
    84     [_board 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*) board
   121 {
   122     return _board;
   123 }
   124 
   125 - (void) setBoard: (GGBLayer*)board
   126 {
   127     setObj(&_board,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         [_board 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( _board )
   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( _board ) {
   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                             NSBeep();
   329                             NSLog(@"WARNING: %@ failed to apply stored move '%@'!", self,move);
   330                             return;
   331                         }
   332                     }@finally{
   333                         turn.replaying = NO;
   334                     }
   335                 }
   336             } else {
   337                 NSLog(@"Reapplying state '%@'",state);
   338                 BeginDisableAnimations();
   339                 self.stateString = state;
   340                 EndDisableAnimations();
   341             }
   342             if( ! [self.stateString isEqual: state] ) {
   343                 _currentTurnNo = oldTurnNo;
   344                 NSBeep();
   345                 NSLog(@"WARNING: %@ failed to apply stored state '%@'!", self,state);
   346                 return;
   347             }
   348         } else
   349             _currentTurnNo = turnNo;
   350     }
   351 }
   352 
   353 
   354 - (BOOL) animateMoveFrom: (CALayer<BitHolder>*)src to: (CALayer<BitHolder>*)dst
   355 {
   356     if( src==nil || dst==nil || dst==src )
   357         return NO;
   358     Bit *bit = [src canDragBit: src.bit];
   359     if( ! bit || ! [dst canDropBit: bit atPoint: GetCGRectCenter(dst.bounds)]
   360               || ! [self canBit: bit moveFrom: src to: dst] )
   361         return NO;
   362     
   363     ChangeSuperlayer(bit, _board.superlayer, -1);
   364     bit.pickedUp = YES;
   365     dst.highlighted = YES;
   366     [bit performSelector: @selector(setPickedUp:) withObject:nil afterDelay: 0.15];
   367     CGPoint endPosition = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: bit.superlayer];
   368     [bit animateAndBlock: @"position"
   369 #if TARGET_OS_IPHONE
   370                     from: [NSValue valueWithCGPoint: bit.position]
   371                       to: [NSValue valueWithCGPoint: endPosition]
   372 #else
   373                     from: [NSValue valueWithPoint: NSPointFromCGPoint(bit.position)]
   374                       to: [NSValue valueWithPoint: NSPointFromCGPoint(endPosition)]
   375 #endif
   376                 duration: 0.25];
   377     dst.bit = bit;
   378     dst.highlighted = NO;
   379     bit.pickedUp = NO;
   380     
   381     [src draggedBit: bit to: dst];
   382     [self bit: bit movedFrom: src to: dst];
   383     return YES;
   384 }
   385 
   386 
   387 - (BOOL) animatePlacementIn: (CALayer<BitHolder>*)dst
   388 {
   389     if( dst == nil )
   390         return NO;
   391     Bit *bit = [self bitToPlaceInHolder: dst];
   392     if( ! bit )
   393         return NO;
   394     
   395     CALayer<BitHolder>* oldHolder = (CALayer<BitHolder>*) bit.holder;
   396     if( oldHolder ) {
   397         if( oldHolder != dst ) 
   398             return [self animateMoveFrom: oldHolder to: dst];
   399     } else
   400         bit.position = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: _board.superlayer];
   401     ChangeSuperlayer(bit, _board.superlayer, -1);
   402     bit.pickedUp = YES;
   403     dst.highlighted = YES;
   404     
   405     DelayFor(0.2);
   406     
   407     dst.bit = bit;
   408     dst.highlighted = NO;
   409     bit.pickedUp = NO;
   410     
   411     [self bit: bit movedFrom: nil to: dst];
   412     return YES;
   413 }
   414      
   415 
   416 #pragma mark -
   417 #pragma mark GAMEPLAY METHODS TO BE OVERRIDDEN:
   418 
   419 
   420 + (NSString*) identifier
   421 {
   422     NSString* name = [self description];
   423     if( [name hasSuffix: @"Game"] )
   424         name = [name substringToIndex: name.length-4];
   425     return name;
   426 }
   427 
   428 + (NSString*) displayName
   429 {
   430     return [self identifier];
   431 }
   432 
   433 + (BOOL) landscapeOriented
   434 {
   435     return NO;
   436 }
   437 
   438 
   439 - (NSString*) initialStateString
   440 {
   441     return @"";
   442 }
   443 
   444 
   445 - (CGImageRef) iconForPlayer: (int)playerIndex
   446 {
   447     return nil;
   448 }
   449 
   450 
   451 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src
   452 {
   453     return YES;
   454 }
   455 
   456 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
   457 {
   458     return YES;
   459 }
   460 
   461 - (void) bit: (Bit*)bit movedFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
   462 {
   463     [self endTurn];
   464 }
   465 
   466 - (Bit*) bitToPlaceInHolder: (id<BitHolder>)holder
   467 {
   468     return nil;
   469 }
   470 
   471 
   472 - (BOOL) clickedBit: (Bit*)bit
   473 {
   474     return YES;
   475 }
   476 
   477 - (Player*) checkForWinner
   478 {
   479     return nil;
   480 }
   481 
   482 /* These are abstract
   483  
   484 - (NSString*) stateString                   {return @"";}
   485 - (void) setStateString: (NSString*)s       { }
   486 
   487 - (BOOL) applyMoveString: (NSString*)move   {return NO;}
   488 */
   489 
   490 @end
   491 
   492 
   493 
   494 
   495 #pragma mark -
   496 @implementation CALayer (Game)
   497 
   498 - (Game*) game
   499 {
   500     // The Game object stores a pointer to itself as the value of the "Game" property
   501     // of its root layer. (CALayers can have arbitrary KV properties stored into them.)
   502     for( CALayer *layer = self; layer; layer=layer.superlayer ) {
   503         Game *game = [layer valueForKey: @"Game"];
   504         if( game )
   505             return game;
   506     }
   507     NSAssert1(NO,@"Couldn't look up Game from %@",self);
   508     return nil;
   509 }
   510 
   511 @end