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