1.1 --- a/Source/Game.m Thu May 29 15:04:06 2008 -0700
1.2 +++ b/Source/Game.m Sat Jul 05 17:46:43 2008 -0700
1.3 @@ -20,71 +20,60 @@
1.4 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
1.5 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1.6 */
1.7 -#import "Game.h"
1.8 -#import "Bit.h"
1.9 -#import "BitHolder.h"
1.10 +#import "Game+Protected.h"
1.11 #import "QuartzUtils.h"
1.12 +#import "GGBUtils.h"
1.13
1.14
1.15 @interface Game ()
1.16 @property (copy) NSArray *players;
1.17 -@property (assign) Player *currentPlayer, *winner;
1.18 +@property (assign) Player *winner;
1.19 +- (void) _startTurn;
1.20 @end
1.21
1.22
1.23 @implementation Game
1.24
1.25
1.26 -+ (NSString*) identifier
1.27 +- (id) init
1.28 {
1.29 - NSString* name = [self description];
1.30 - if( [name hasSuffix: @"Game"] )
1.31 - name = [name substringToIndex: name.length-4];
1.32 - return name;
1.33 -}
1.34 -
1.35 -
1.36 -+ (NSString*) displayName
1.37 -{
1.38 - return [self identifier];
1.39 -}
1.40 -
1.41 -
1.42 -- (id) initWithUniqueID: (NSString*)uuid
1.43 -{
1.44 - NSParameterAssert(uuid);
1.45 self = [super init];
1.46 if (self != nil) {
1.47 - _uniqueID = [uuid copy];
1.48 - _board = [[GGBLayer alloc] init];
1.49 - // Store a pointer to myself as the value of the "Game" property
1.50 - // of my root layer. (CALayers can have arbitrary KV properties stored into them.)
1.51 - // This is used by the -[CALayer game] category method defined below, to find the Game.
1.52 - [_board setValue: self forKey: @"Game"];
1.53 -
1.54 - _currentMove = [[NSMutableString alloc] init];
1.55 + // Don't create _turns till -initWithCoder or -setNumberOfPlayers:.
1.56 }
1.57 return self;
1.58 }
1.59
1.60 -- (id) init
1.61 +
1.62 +- (id) initWithCoder: (NSCoder*)decoder
1.63 {
1.64 - CFUUIDRef uuidRef = CFUUIDCreate(NULL);
1.65 - NSString* uuid = (id) CFUUIDCreateString(NULL,uuidRef);
1.66 - self = [self initWithUniqueID: uuid];
1.67 - CFRelease(uuid);
1.68 - CFRelease(uuidRef);
1.69 + self = [self init];
1.70 + if( self ) {
1.71 + _players = [[decoder decodeObjectForKey: @"players"] mutableCopy];
1.72 + _winner = [decoder decodeObjectForKey: @"winner"];
1.73 + _turns = [[decoder decodeObjectForKey: @"turns"] mutableCopy];
1.74 + _extraValues = [[decoder decodeObjectForKey: @"extraValues"] mutableCopy];
1.75 + self.currentTurnNo = self.maxTurnNo;
1.76 + }
1.77 return self;
1.78 }
1.79
1.80 -- (id) initWithBoard: (GGBLayer*)board
1.81 +
1.82 +- (void) encodeWithCoder: (NSCoder*)coder
1.83 +{
1.84 + [coder encodeObject: _players forKey: @"players"];
1.85 + [coder encodeObject: _winner forKey: @"winner"];
1.86 + [coder encodeObject: _turns forKey: @"turns"];
1.87 + [coder encodeObject: _extraValues forKey: @"extraValues"];
1.88 +}
1.89 +
1.90 +
1.91 +- (id) initNewGameWithBoard: (GGBLayer*)board
1.92 {
1.93 self = [self init];
1.94 - if (self != nil) {
1.95 - _states = [[NSMutableArray alloc] init];
1.96 - _moves = [[NSMutableArray alloc] init];
1.97 - _board = [board retain];
1.98 - [board setValue: self forKey: @"Game"];
1.99 + if( self ) {
1.100 + self.board = board;
1.101 + NSAssert1(_players && _turns, @"%@ failed to set numberOfPlayers",self);
1.102 }
1.103 return self;
1.104 }
1.105 @@ -94,15 +83,67 @@
1.106 {
1.107 [_board release];
1.108 [_players release];
1.109 - [_currentMove release];
1.110 - [_states release];
1.111 - [_moves release];
1.112 + [_turns release];
1.113 + [_extraValues release];
1.114 [super dealloc];
1.115 }
1.116
1.117
1.118 -@synthesize players=_players, currentPlayer=_currentPlayer, winner=_winner,
1.119 - currentMove=_currentMove, states=_states, moves=_moves, uniqueID=_uniqueID;
1.120 +@synthesize players=_players, winner=_winner, turns=_turns, requireConfirmation=_requireConfirmation;
1.121 +
1.122 +
1.123 +- (id)valueForUndefinedKey:(NSString *)key
1.124 +{
1.125 + return [_extraValues objectForKey: key];
1.126 +}
1.127 +
1.128 +- (void)setValue:(id)value forUndefinedKey:(NSString *)key
1.129 +{
1.130 + if( ! _extraValues )
1.131 + _extraValues = [[NSMutableDictionary alloc] init];
1.132 + if( value )
1.133 + [_extraValues setObject: value forKey: key];
1.134 + else
1.135 + [_extraValues removeObjectForKey: key];
1.136 +}
1.137 +
1.138 +
1.139 +#pragma mark -
1.140 +#pragma mark BOARD:
1.141 +
1.142 +
1.143 +- (void) setUpBoard
1.144 +{
1.145 + NSAssert1(NO,@"%@ forgot to implement -setUpBoard",[self class]);
1.146 +}
1.147 +
1.148 +- (GGBLayer*) board
1.149 +{
1.150 + return _board;
1.151 +}
1.152 +
1.153 +- (void) setBoard: (GGBLayer*)board
1.154 +{
1.155 + setObj(&_board,board);
1.156 + // Store a pointer to myself as the value of the "Game" property
1.157 + // of my root layer. (CALayers can have arbitrary KV properties stored into them.)
1.158 + // This is used by the -[CALayer game] category method defined below, to find the Game.
1.159 + [_board setValue: self forKey: @"Game"];
1.160 +
1.161 + BeginDisableAnimations();
1.162 +
1.163 + // Tell the game to add the necessary bits to the board:
1.164 + [self setUpBoard];
1.165 +
1.166 + // Re-apply the current state to set up the pieces/cards:
1.167 + self.stateString = [[_turns objectAtIndex: _currentTurnNo] boardState];
1.168 +
1.169 + EndDisableAnimations();
1.170 +}
1.171 +
1.172 +
1.173 +#pragma mark -
1.174 +#pragma mark PLAYERS:
1.175
1.176
1.177 - (void) setNumberOfPlayers: (unsigned)n
1.178 @@ -114,105 +155,184 @@
1.179 [players addObject: player];
1.180 [player release];
1.181 }
1.182 + self.players = players;
1.183 self.winner = nil;
1.184 - self.currentPlayer = nil;
1.185 - self.players = players;
1.186 +
1.187 + Turn *turn = [[Turn alloc] initStartOfGame: self];
1.188 + setObj(&_turns, [NSMutableArray arrayWithObject: turn]);
1.189 + [turn release];
1.190 + [self _startTurn];
1.191 }
1.192
1.193 -
1.194 -- (void) addToMove: (NSString*)str;
1.195 +- (Player*) remotePlayer
1.196 {
1.197 - [_currentMove appendString: str];
1.198 + for( Player *player in _players )
1.199 + if( ! player.local )
1.200 + return player;
1.201 + return nil;
1.202 }
1.203
1.204 -
1.205 -- (BOOL) _rememberState
1.206 +- (BOOL) isLocal
1.207 {
1.208 - if( self.isLatestTurn ) {
1.209 - [_states addObject: self.stateString];
1.210 - return YES;
1.211 - } else
1.212 - return NO;
1.213 + return self.remotePlayer == nil;
1.214 }
1.215
1.216 +- (Player*) currentPlayer
1.217 +{
1.218 + return self.currentTurn.player;
1.219 +}
1.220
1.221 -- (void) nextPlayer
1.222 ++ (NSArray*) keyPathsForValuesAffectingCurrentPlayer {return [NSArray arrayWithObject: @"currentTurn"];}
1.223 +
1.224 +
1.225 +#pragma mark -
1.226 +#pragma mark TURNS:
1.227 +
1.228 +
1.229 +- (Turn*) currentTurn
1.230 {
1.231 - BOOL latestTurn = [self _rememberState];
1.232 - if( ! _currentPlayer ) {
1.233 - NSLog(@"*** The %@ Begins! ***", self.class);
1.234 - self.currentPlayer = [_players objectAtIndex: 0];
1.235 - } else {
1.236 - self.currentPlayer = _currentPlayer.nextPlayer;
1.237 - if( latestTurn ) {
1.238 - [self willChangeValueForKey: @"currentTurn"];
1.239 - _currentTurn++;
1.240 - [self didChangeValueForKey: @"currentTurn"];
1.241 - }
1.242 - }
1.243 - NSLog(@"Current player is %@",_currentPlayer);
1.244 + return [_turns objectAtIndex: _currentTurnNo];
1.245 +}
1.246 +
1.247 +- (Turn*) latestTurn
1.248 +{
1.249 + return [_turns lastObject];
1.250 +}
1.251 +
1.252 ++ (NSArray*) keyPathsForValuesAffectingCurrentTurn {return [NSArray arrayWithObject: @"currentTurnNo"];}
1.253 ++ (NSArray*) keyPathsForValuesAffectingLatestTurn {return [NSArray arrayWithObject: @"turns"];}
1.254 +
1.255 +
1.256 +- (void) _startTurn
1.257 +{
1.258 + Turn *lastTurn = [_turns lastObject];
1.259 + NSAssert(lastTurn.status==kTurnFinished,@"Can't _startTurn till previous turn is finished");
1.260 + Turn *newTurn = [[Turn alloc] initWithPlayer: lastTurn.nextPlayer];
1.261 +
1.262 + [self willChangeValueForKey: @"turns"];
1.263 + [_turns addObject: newTurn];
1.264 + [self willChangeValueForKey: @"turns"];
1.265 + [newTurn release];
1.266 + self.currentTurnNo = _turns.count-1;
1.267 }
1.268
1.269
1.270 - (void) endTurn
1.271 {
1.272 - NSLog(@"--- End of turn (move was '%@')", _currentMove);
1.273 - if( self.isLatestTurn ) {
1.274 - NSString *move = [[_currentMove copy] autorelease];
1.275 - [_currentMove setString: @""];
1.276 - [self willChangeValueForKey: @"maxTurn"];
1.277 - [_moves addObject: move];
1.278 - [self didChangeValueForKey: @"maxTurn"];
1.279 - }
1.280 + Turn *curTurn = self.currentTurn;
1.281 + if( curTurn.isLatestTurn && ! curTurn.replaying ) {
1.282 + curTurn.status = kTurnComplete;
1.283 + NSLog(@"--- End of %@", curTurn);
1.284 +
1.285 + Player *winner = [self checkForWinner];
1.286 + if( winner ) {
1.287 + NSLog(@"*** The %@ Ends! The winner is %@ ! ***", self.class, winner);
1.288 + self.winner = winner;
1.289 + }
1.290 +
1.291 + if( ! _requireConfirmation || !curTurn.player.local )
1.292 + [self confirmCurrentTurn];
1.293
1.294 - Player *winner = [self checkForWinner];
1.295 - if( winner ) {
1.296 - NSLog(@"*** The %@ Ends! The winner is %@ ! ***", self.class, winner);
1.297 - [self _rememberState];
1.298 - self.winner = winner;
1.299 - } else
1.300 - [self nextPlayer];
1.301 -}
1.302 -
1.303 -
1.304 -#pragma mark -
1.305 -#pragma mark STORED TURNS:
1.306 -
1.307 -
1.308 -- (unsigned) maxTurn
1.309 -{
1.310 - return _moves.count;
1.311 -}
1.312 -
1.313 -- (unsigned) currentTurn
1.314 -{
1.315 - return _currentTurn;
1.316 -}
1.317 -
1.318 -- (void) setCurrentTurn: (unsigned)turn
1.319 -{
1.320 - NSParameterAssert(turn<=self.maxTurn);
1.321 - if( turn != _currentTurn ) {
1.322 - if( turn==_currentTurn+1 ) {
1.323 - [self applyMoveString: [_moves objectAtIndex: _currentTurn]];
1.324 - } else {
1.325 - BeginDisableAnimations();
1.326 - self.stateString = [_states objectAtIndex: turn];
1.327 - EndDisableAnimations();
1.328 - }
1.329 - _currentTurn = turn;
1.330 - self.currentPlayer = [_players objectAtIndex: (turn % _players.count)];
1.331 + [[NSNotificationCenter defaultCenter] postNotificationName: kTurnCompleteNotification
1.332 + object: curTurn];
1.333 }
1.334 }
1.335
1.336 +- (void) cancelCurrentTurn
1.337 +{
1.338 + Turn *curTurn = self.currentTurn;
1.339 + if( curTurn.status > kTurnEmpty && curTurn.status < kTurnFinished ) {
1.340 + if( _winner )
1.341 + self.winner = nil;
1.342 + if( _board )
1.343 + self.stateString = curTurn.previousTurn.boardState;
1.344 + curTurn.status = kTurnEmpty;
1.345 + }
1.346 +}
1.347 +
1.348 +- (void) confirmCurrentTurn
1.349 +{
1.350 + Turn *curTurn = self.currentTurn;
1.351 + if( curTurn.status == kTurnComplete ) {
1.352 + curTurn.status = kTurnFinished;
1.353 + if( ! _winner )
1.354 + [self _startTurn];
1.355 + }
1.356 +}
1.357 +
1.358
1.359 - (BOOL) isLatestTurn
1.360 {
1.361 - return _currentTurn == MAX(_states.count,1)-1;
1.362 + return _currentTurnNo == _turns.count-1;
1.363 }
1.364
1.365 +- (unsigned) maxTurnNo
1.366 +{
1.367 + return _turns.count-1;
1.368 +}
1.369
1.370 -- (BOOL) animateMoveFrom: (BitHolder*)src to: (BitHolder*)dst
1.371 ++ (NSArray*) keyPathsForValuesAffectingIsLatestTurn {return [NSArray arrayWithObjects: @"currentTurnNo",@"turns",nil];}
1.372 ++ (NSArray*) keyPathsForValuesAffectingMaxTurnNo {return [NSArray arrayWithObjects: @"turns",nil];}
1.373 +
1.374 +- (unsigned) currentTurnNo
1.375 +{
1.376 + return _currentTurnNo;
1.377 +}
1.378 +
1.379 +
1.380 +#pragma mark -
1.381 +#pragma mark REPLAYING TURNS:
1.382 +
1.383 +
1.384 +- (void) setCurrentTurnNo: (unsigned)turnNo
1.385 +{
1.386 + NSParameterAssert(turnNo<=self.maxTurnNo);
1.387 + unsigned oldTurnNo = _currentTurnNo;
1.388 + if( turnNo != oldTurnNo ) {
1.389 + if( _board ) {
1.390 + Turn *turn = [_turns objectAtIndex: turnNo];
1.391 + NSString *state;
1.392 + if( turn.status == kTurnEmpty )
1.393 + state = turn.previousTurn.boardState;
1.394 + else
1.395 + state = turn.boardState;
1.396 + NSAssert1(state,@"empty boardState at turn #%i",turnNo);
1.397 + _currentTurnNo = turnNo;
1.398 + if( turnNo==oldTurnNo+1 ) {
1.399 + NSString *move = turn.move;
1.400 + if( move ) {
1.401 + NSLog(@"Reapplying move '%@'",move);
1.402 + turn.replaying = YES;
1.403 + @try{
1.404 + if( ! [self applyMoveString: move] ) {
1.405 + _currentTurnNo = oldTurnNo;
1.406 + NSBeep();
1.407 + NSLog(@"WARNING: %@ failed to apply stored move '%@'!", self,move);
1.408 + return;
1.409 + }
1.410 + }@finally{
1.411 + turn.replaying = NO;
1.412 + }
1.413 + }
1.414 + } else {
1.415 + NSLog(@"Reapplying state '%@'",state);
1.416 + BeginDisableAnimations();
1.417 + self.stateString = state;
1.418 + EndDisableAnimations();
1.419 + }
1.420 + if( ! [self.stateString isEqual: state] ) {
1.421 + _currentTurnNo = oldTurnNo;
1.422 + NSBeep();
1.423 + NSLog(@"WARNING: %@ failed to apply stored state '%@'!", self,state);
1.424 + return;
1.425 + }
1.426 + } else
1.427 + _currentTurnNo = turnNo;
1.428 + }
1.429 +}
1.430 +
1.431 +
1.432 +- (BOOL) animateMoveFrom: (CALayer<BitHolder>*)src to: (CALayer<BitHolder>*)dst
1.433 {
1.434 if( src==nil || dst==nil || dst==src )
1.435 return NO;
1.436 @@ -241,7 +361,35 @@
1.437
1.438 [src draggedBit: bit to: dst];
1.439 [self bit: bit movedFrom: src to: dst];
1.440 - src = dst;
1.441 + return YES;
1.442 +}
1.443 +
1.444 +
1.445 +- (BOOL) animatePlacementIn: (CALayer<BitHolder>*)dst
1.446 +{
1.447 + if( dst == nil )
1.448 + return NO;
1.449 + Bit *bit = [self bitToPlaceInHolder: dst];
1.450 + if( ! bit )
1.451 + return NO;
1.452 +
1.453 + CALayer<BitHolder>* oldHolder = (CALayer<BitHolder>*) bit.holder;
1.454 + if( oldHolder ) {
1.455 + if( oldHolder != dst )
1.456 + return [self animateMoveFrom: oldHolder to: dst];
1.457 + } else
1.458 + bit.position = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: _board.superlayer];
1.459 + ChangeSuperlayer(bit, _board.superlayer, -1);
1.460 + bit.pickedUp = YES;
1.461 + dst.highlighted = YES;
1.462 +
1.463 + DelayFor(0.2);
1.464 +
1.465 + dst.bit = bit;
1.466 + dst.highlighted = NO;
1.467 + bit.pickedUp = NO;
1.468 +
1.469 + [self bit: bit movedFrom: nil to: dst];
1.470 return YES;
1.471 }
1.472
1.473 @@ -250,12 +398,37 @@
1.474 #pragma mark GAMEPLAY METHODS TO BE OVERRIDDEN:
1.475
1.476
1.477 ++ (NSString*) identifier
1.478 +{
1.479 + NSString* name = [self description];
1.480 + if( [name hasSuffix: @"Game"] )
1.481 + name = [name substringToIndex: name.length-4];
1.482 + return name;
1.483 +}
1.484 +
1.485 ++ (NSString*) displayName
1.486 +{
1.487 + return [self identifier];
1.488 +}
1.489 +
1.490 + (BOOL) landscapeOriented
1.491 {
1.492 return NO;
1.493 }
1.494
1.495
1.496 +- (NSString*) initialStateString
1.497 +{
1.498 + return @"";
1.499 +}
1.500 +
1.501 +
1.502 +- (CGImageRef) iconForPlayer: (int)playerIndex
1.503 +{
1.504 + return nil;
1.505 +}
1.506 +
1.507 +
1.508 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src
1.509 {
1.510 return YES;
1.511 @@ -287,83 +460,20 @@
1.512 return nil;
1.513 }
1.514
1.515 -
1.516 +/* These are abstract
1.517 +
1.518 - (NSString*) stateString {return @"";}
1.519 - (void) setStateString: (NSString*)s { }
1.520
1.521 - (BOOL) applyMoveString: (NSString*)move {return NO;}
1.522 +*/
1.523
1.524 @end
1.525
1.526
1.527
1.528
1.529 -@implementation Player
1.530 -
1.531 -
1.532 -- (id) initWithGame: (Game*)game
1.533 -{
1.534 - self = [super init];
1.535 - if (self != nil) {
1.536 - _game = game;
1.537 - }
1.538 - return self;
1.539 -}
1.540 -
1.541 -- (id) initWithCoder: (NSCoder*)decoder
1.542 -{
1.543 - self = [self init];
1.544 - if( self ) {
1.545 - _game = [decoder decodeObjectForKey: @"game"];
1.546 - _name = [[decoder decodeObjectForKey: @"name"] copy];
1.547 - }
1.548 - return self;
1.549 -}
1.550 -
1.551 -- (void) encodeWithCoder: (NSCoder*)coder
1.552 -{
1.553 - [coder encodeObject: _game forKey: @"game"];
1.554 - [coder encodeObject: _name forKey: @"name"];
1.555 -}
1.556 -
1.557 -- (void) dealloc
1.558 -{
1.559 - [_name release];
1.560 - [super dealloc];
1.561 -}
1.562 -
1.563 -
1.564 -@synthesize game=_game, name=_name;
1.565 -
1.566 -- (BOOL) isCurrent {return self == _game.currentPlayer;}
1.567 -- (BOOL) isFriendly {return self == _game.currentPlayer;} // could be overridden for games with partners
1.568 -- (BOOL) isUnfriendly {return ! self.friendly;}
1.569 -
1.570 -- (int) index
1.571 -{
1.572 - return [_game.players indexOfObjectIdenticalTo: self];
1.573 -}
1.574 -
1.575 -- (Player*) nextPlayer
1.576 -{
1.577 - return [_game.players objectAtIndex: (self.index+1) % _game.players.count];
1.578 -}
1.579 -
1.580 -- (Player*) previousPlayer
1.581 -{
1.582 - return [_game.players objectAtIndex: (self.index-1) % _game.players.count];
1.583 -}
1.584 -
1.585 -- (NSString*) description
1.586 -{
1.587 - return [NSString stringWithFormat: @"%@[%@]", self.class,self.name];
1.588 -}
1.589 -
1.590 -@end
1.591 -
1.592 -
1.593 -
1.594 -
1.595 +#pragma mark -
1.596 @implementation CALayer (Game)
1.597
1.598 - (Game*) game