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.
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) initNewGameWithBoard: (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) setBoard: (GGBLayer*)board
127 setObj(&_board,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"];
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 *curTurn = self.currentTurn;
225 if( curTurn.isLatestTurn && ! curTurn.replaying ) {
226 curTurn.status = kTurnComplete;
227 NSLog(@"--- End of %@", curTurn);
229 Player *winner = [self checkForWinner];
231 NSLog(@"*** The %@ Ends! The winner is %@ ! ***", self.class, winner);
232 self.winner = winner;
235 if( ! _requireConfirmation || !curTurn.player.local )
236 [self confirmCurrentTurn];
238 [[NSNotificationCenter defaultCenter] postNotificationName: kTurnCompleteNotification
243 - (void) cancelCurrentTurn
245 Turn *curTurn = self.currentTurn;
246 if( curTurn.status > kTurnEmpty && curTurn.status < kTurnFinished ) {
250 self.stateString = curTurn.previousTurn.boardState;
251 curTurn.status = kTurnEmpty;
255 - (void) confirmCurrentTurn
257 Turn *curTurn = self.currentTurn;
258 if( curTurn.status == kTurnComplete ) {
259 curTurn.status = kTurnFinished;
266 - (BOOL) isLatestTurn
268 return _currentTurnNo == _turns.count-1;
271 - (unsigned) maxTurnNo
273 return _turns.count-1;
276 + (NSArray*) keyPathsForValuesAffectingIsLatestTurn {return [NSArray arrayWithObjects: @"currentTurnNo",@"turns",nil];}
277 + (NSArray*) keyPathsForValuesAffectingMaxTurnNo {return [NSArray arrayWithObjects: @"turns",nil];}
279 - (unsigned) currentTurnNo
281 return _currentTurnNo;
286 #pragma mark REPLAYING TURNS:
289 - (void) setCurrentTurnNo: (unsigned)turnNo
291 NSParameterAssert(turnNo<=self.maxTurnNo);
292 unsigned oldTurnNo = _currentTurnNo;
293 if( turnNo != oldTurnNo ) {
295 Turn *turn = [_turns objectAtIndex: turnNo];
297 if( turn.status == kTurnEmpty )
298 state = turn.previousTurn.boardState;
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;
306 NSLog(@"Reapplying move '%@'",move);
307 turn.replaying = YES;
309 if( ! [self applyMoveString: move] ) {
310 _currentTurnNo = oldTurnNo;
312 NSLog(@"WARNING: %@ failed to apply stored move '%@'!", self,move);
320 NSLog(@"Reapplying state '%@'",state);
321 BeginDisableAnimations();
322 self.stateString = state;
323 EndDisableAnimations();
325 if( ! [self.stateString isEqual: state] ) {
326 _currentTurnNo = oldTurnNo;
328 NSLog(@"WARNING: %@ failed to apply stored state '%@'!", self,state);
332 _currentTurnNo = turnNo;
337 - (BOOL) animateMoveFrom: (CALayer<BitHolder>*)src to: (CALayer<BitHolder>*)dst
339 if( src==nil || dst==nil || dst==src )
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] )
346 ChangeSuperlayer(bit, _board.superlayer, -1);
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"
353 from: [NSValue valueWithCGPoint: bit.position]
354 to: [NSValue valueWithCGPoint: endPosition]
356 from: [NSValue valueWithPoint: NSPointFromCGPoint(bit.position)]
357 to: [NSValue valueWithPoint: NSPointFromCGPoint(endPosition)]
361 dst.highlighted = NO;
364 [src draggedBit: bit to: dst];
365 [self bit: bit movedFrom: src to: dst];
370 - (BOOL) animatePlacementIn: (CALayer<BitHolder>*)dst
374 Bit *bit = [self bitToPlaceInHolder: dst];
378 CALayer<BitHolder>* oldHolder = (CALayer<BitHolder>*) bit.holder;
380 if( oldHolder != dst )
381 return [self animateMoveFrom: oldHolder to: dst];
383 bit.position = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: _board.superlayer];
384 ChangeSuperlayer(bit, _board.superlayer, -1);
386 dst.highlighted = YES;
391 dst.highlighted = NO;
394 [self bit: bit movedFrom: nil to: dst];
400 #pragma mark GAMEPLAY METHODS TO BE OVERRIDDEN:
403 + (NSString*) identifier
405 NSString* name = [self description];
406 if( [name hasSuffix: @"Game"] )
407 name = [name substringToIndex: name.length-4];
411 + (NSString*) displayName
413 return [self identifier];
416 + (BOOL) landscapeOriented
422 - (NSString*) initialStateString
428 - (CGImageRef) iconForPlayer: (int)playerIndex
434 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src
439 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
444 - (void) bit: (Bit*)bit movedFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
449 - (Bit*) bitToPlaceInHolder: (id<BitHolder>)holder
455 - (BOOL) clickedBit: (Bit*)bit
460 - (Player*) checkForWinner
465 /* These are abstract
467 - (NSString*) stateString {return @"";}
468 - (void) setStateString: (NSString*)s { }
470 - (BOOL) applyMoveString: (NSString*)move {return NO;}
479 @implementation CALayer (Game)
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"];
490 NSAssert1(NO,@"Couldn't look up Game from %@",self);