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