Don't load Checkers sound files till a game is displayed on a board; this speeds up launch.
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.
5 Redistribution and use in source and binary forms, with or without modification, are permitted
6 provided that the following conditions are met:
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.
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.
23 #import "Game+Protected.h"
24 #import "QuartzUtils.h"
29 @property (copy) NSArray *players;
30 @property (assign) Player *winner;
42 // Don't create _turns till -initWithCoder or -setNumberOfPlayers:.
48 - (id) initWithCoder: (NSCoder*)decoder
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;
62 - (void) encodeWithCoder: (NSCoder*)coder
64 [coder encodeObject: _players forKey: @"players"];
65 [coder encodeObject: _winner forKey: @"winner"];
66 [coder encodeObject: _turns forKey: @"turns"];
67 [coder encodeObject: _extraValues forKey: @"extraValues"];
71 - (id) initNewGameWithTable: (GGBLayer*)board
76 NSAssert1(_players && _turns, @"%@ failed to set numberOfPlayers",self);
87 [_extraValues release];
92 @synthesize players=_players, winner=_winner, turns=_turns, requireConfirmation=_requireConfirmation;
95 - (id)valueForUndefinedKey:(NSString *)key
97 return [_extraValues objectForKey: key];
100 - (void)setValue:(id)value forUndefinedKey:(NSString *)key
103 _extraValues = [[NSMutableDictionary alloc] init];
105 [_extraValues setObject: value forKey: key];
107 [_extraValues removeObjectForKey: key];
117 NSAssert1(NO,@"%@ forgot to implement -setUpBoard",[self class]);
125 - (void) setTable: (GGBLayer*)board
127 setObj(&_table,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"];
134 BeginDisableAnimations();
136 // Tell the game to add the necessary bits to the board:
139 // Re-apply the current state to set up the pieces/cards:
140 self.stateString = [[_turns objectAtIndex: _currentTurnNo] boardState];
142 EndDisableAnimations();
148 #pragma mark PLAYERS:
151 - (void) setNumberOfPlayers: (unsigned)n
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];
160 self.players = players;
163 Turn *turn = [[Turn alloc] initStartOfGame: self];
164 setObj(&_turns, [NSMutableArray arrayWithObject: turn]);
169 - (Player*) remotePlayer
171 for( Player *player in _players )
179 return self.remotePlayer == nil;
182 - (Player*) currentPlayer
184 return self.currentTurn.player;
187 + (NSArray*) keyPathsForValuesAffectingCurrentPlayer {return [NSArray arrayWithObject: @"currentTurn"];}
194 - (Turn*) currentTurn
196 return [_turns objectAtIndex: _currentTurnNo];
201 return [_turns lastObject];
204 + (NSArray*) keyPathsForValuesAffectingCurrentTurn {return [NSArray arrayWithObject: @"currentTurnNo"];}
205 + (NSArray*) keyPathsForValuesAffectingLatestTurn {return [NSArray arrayWithObject: @"turns"];}
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];
214 [self willChangeValueForKey: @"turns"];
215 [_turns addObject: newTurn];
216 [self willChangeValueForKey: @"turns"];
218 self.currentTurnNo = _turns.count-1;
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;
232 if( _currentTurnNo==latestTurnNo )
241 Turn *curTurn = self.currentTurn;
242 if( curTurn.isLatestTurn && ! curTurn.replaying ) {
243 curTurn.status = kTurnComplete;
244 NSLog(@"--- End of %@", curTurn);
246 Player *winner = [self checkForWinner];
248 NSLog(@"*** The %@ Ends! The winner is %@ ! ***", self.class, winner);
249 self.winner = winner;
252 if( ! _requireConfirmation || !curTurn.player.local )
253 [self confirmCurrentTurn];
255 [[NSNotificationCenter defaultCenter] postNotificationName: kTurnCompleteNotification
260 - (void) cancelCurrentTurn
262 Turn *curTurn = self.currentTurn;
263 if( curTurn.status > kTurnEmpty && curTurn.status < kTurnFinished ) {
267 self.stateString = curTurn.previousTurn.boardState;
268 curTurn.status = kTurnEmpty;
272 - (void) confirmCurrentTurn
274 Turn *curTurn = self.currentTurn;
275 if( curTurn.status == kTurnComplete ) {
276 curTurn.status = kTurnFinished;
283 - (BOOL) isLatestTurn
285 return _currentTurnNo == _turns.count-1;
288 - (unsigned) maxTurnNo
290 return _turns.count-1;
293 + (NSArray*) keyPathsForValuesAffectingIsLatestTurn {return [NSArray arrayWithObjects: @"currentTurnNo",@"turns",nil];}
294 + (NSArray*) keyPathsForValuesAffectingMaxTurnNo {return [NSArray arrayWithObjects: @"turns",nil];}
296 - (unsigned) currentTurnNo
298 return _currentTurnNo;
303 #pragma mark REPLAYING TURNS:
306 - (void) setCurrentTurnNo: (unsigned)turnNo
308 NSParameterAssert(turnNo<=self.maxTurnNo);
309 unsigned oldTurnNo = _currentTurnNo;
310 if( turnNo != oldTurnNo ) {
312 Turn *turn = [_turns objectAtIndex: turnNo];
314 if( turn.status == kTurnEmpty )
315 state = turn.previousTurn.boardState;
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;
323 NSLog(@"Reapplying move '%@'",move);
324 turn.replaying = YES;
326 if( ! [self applyMoveString: move] ) {
327 _currentTurnNo = oldTurnNo;
328 NSLog(@"WARNING: %@ failed to apply stored move '%@'!", self,move);
336 NSLog(@"Reapplying state '%@'",state);
337 BeginDisableAnimations();
338 self.stateString = state;
339 EndDisableAnimations();
341 if( ! [self.stateString isEqual: state] ) {
342 _currentTurnNo = oldTurnNo;
343 NSLog(@"WARNING: %@ failed to apply stored state '%@'!", self,state);
347 _currentTurnNo = turnNo;
352 - (BOOL) animateMoveFrom: (CALayer<BitHolder>*)src to: (CALayer<BitHolder>*)dst
354 if( src==nil || dst==nil || dst==src )
356 Bit *bit = [src canDragBit: src.bit];
357 if( ! bit || ! [dst canDropBit: bit atPoint: GetCGRectCenter(dst.bounds)]
358 || ! [self canBit: bit moveFrom: src to: dst] )
361 ChangeSuperlayer(bit, _table.superlayer, -1);
363 dst.highlighted = YES;
364 [bit performSelector: @selector(setPickedUp:) withObject:nil afterDelay: 0.15];
365 CGPoint endPosition = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: bit.superlayer];
366 [bit animateAndBlock: @"position"
368 from: [NSValue valueWithCGPoint: bit.position]
369 to: [NSValue valueWithCGPoint: endPosition]
371 from: [NSValue valueWithPoint: NSPointFromCGPoint(bit.position)]
372 to: [NSValue valueWithPoint: NSPointFromCGPoint(endPosition)]
376 dst.highlighted = NO;
379 [src draggedBit: bit to: dst];
380 [self bit: bit movedFrom: src to: dst];
385 - (BOOL) animatePlacementIn: (CALayer<BitHolder>*)dst
389 Bit *bit = [self bitToPlaceInHolder: dst];
393 CALayer<BitHolder>* oldHolder = (CALayer<BitHolder>*) bit.holder;
395 if( oldHolder != dst )
396 return [self animateMoveFrom: oldHolder to: dst];
398 bit.position = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: _table.superlayer];
399 ChangeSuperlayer(bit, _table.superlayer, -1);
401 dst.highlighted = YES;
406 dst.highlighted = NO;
409 [self bit: bit movedFrom: nil to: dst];
415 #pragma mark GAMEPLAY METHODS TO BE OVERRIDDEN:
418 + (NSString*) identifier
420 NSString* name = [self description];
421 if( [name hasSuffix: @"Game"] )
422 name = [name substringToIndex: name.length-4];
426 + (NSString*) displayName
428 return [self identifier];
431 + (BOOL) landscapeOriented
437 - (NSString*) initialStateString
443 - (CGImageRef) iconForPlayer: (int)playerIndex
449 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src
454 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
459 - (void) bit: (Bit*)bit movedFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
464 - (Bit*) bitToPlaceInHolder: (id<BitHolder>)holder
470 - (BOOL) clickedBit: (Bit*)bit
475 - (Player*) checkForWinner
480 /* These are abstract
482 - (NSString*) stateString {return @"";}
483 - (void) setStateString: (NSString*)s { }
485 - (BOOL) applyMoveString: (NSString*)move {return NO;}
494 @implementation CALayer (Game)
498 // The Game object stores a pointer to itself as the value of the "Game" property
499 // of its root layer. (CALayers can have arbitrary KV properties stored into them.)
500 for( CALayer *layer = self; layer; layer=layer.superlayer ) {
501 Game *game = [layer valueForKey: @"Game"];
505 NSAssert1(NO,@"Couldn't look up Game from %@",self);