Lots of reworking. Completed support for game history, including Turn class. Changed Game API around quite a bit.
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);
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"];
133 BeginDisableAnimations();
135 // Tell the game to add the necessary bits to the board:
138 // Re-apply the current state to set up the pieces/cards:
139 self.stateString = [[_turns objectAtIndex: _currentTurnNo] boardState];
141 EndDisableAnimations();
146 #pragma mark PLAYERS:
149 - (void) setNumberOfPlayers: (unsigned)n
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];
158 self.players = players;
161 Turn *turn = [[Turn alloc] initStartOfGame: self];
162 setObj(&_turns, [NSMutableArray arrayWithObject: turn]);
167 - (Player*) remotePlayer
169 for( Player *player in _players )
177 return self.remotePlayer == nil;
180 - (Player*) currentPlayer
182 return self.currentTurn.player;
185 + (NSArray*) keyPathsForValuesAffectingCurrentPlayer {return [NSArray arrayWithObject: @"currentTurn"];}
192 - (Turn*) currentTurn
194 return [_turns objectAtIndex: _currentTurnNo];
199 return [_turns lastObject];
202 + (NSArray*) keyPathsForValuesAffectingCurrentTurn {return [NSArray arrayWithObject: @"currentTurnNo"];}
203 + (NSArray*) keyPathsForValuesAffectingLatestTurn {return [NSArray arrayWithObject: @"turns"];}
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];
212 [self willChangeValueForKey: @"turns"];
213 [_turns addObject: newTurn];
214 [self willChangeValueForKey: @"turns"];
216 self.currentTurnNo = _turns.count-1;
222 Turn *curTurn = self.currentTurn;
223 if( curTurn.isLatestTurn && ! curTurn.replaying ) {
224 curTurn.status = kTurnComplete;
225 NSLog(@"--- End of %@", curTurn);
227 Player *winner = [self checkForWinner];
229 NSLog(@"*** The %@ Ends! The winner is %@ ! ***", self.class, winner);
230 self.winner = winner;
233 if( ! _requireConfirmation || !curTurn.player.local )
234 [self confirmCurrentTurn];
236 [[NSNotificationCenter defaultCenter] postNotificationName: kTurnCompleteNotification
241 - (void) cancelCurrentTurn
243 Turn *curTurn = self.currentTurn;
244 if( curTurn.status > kTurnEmpty && curTurn.status < kTurnFinished ) {
248 self.stateString = curTurn.previousTurn.boardState;
249 curTurn.status = kTurnEmpty;
253 - (void) confirmCurrentTurn
255 Turn *curTurn = self.currentTurn;
256 if( curTurn.status == kTurnComplete ) {
257 curTurn.status = kTurnFinished;
264 - (BOOL) isLatestTurn
266 return _currentTurnNo == _turns.count-1;
269 - (unsigned) maxTurnNo
271 return _turns.count-1;
274 + (NSArray*) keyPathsForValuesAffectingIsLatestTurn {return [NSArray arrayWithObjects: @"currentTurnNo",@"turns",nil];}
275 + (NSArray*) keyPathsForValuesAffectingMaxTurnNo {return [NSArray arrayWithObjects: @"turns",nil];}
277 - (unsigned) currentTurnNo
279 return _currentTurnNo;
284 #pragma mark REPLAYING TURNS:
287 - (void) setCurrentTurnNo: (unsigned)turnNo
289 NSParameterAssert(turnNo<=self.maxTurnNo);
290 unsigned oldTurnNo = _currentTurnNo;
291 if( turnNo != oldTurnNo ) {
293 Turn *turn = [_turns objectAtIndex: turnNo];
295 if( turn.status == kTurnEmpty )
296 state = turn.previousTurn.boardState;
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;
304 NSLog(@"Reapplying move '%@'",move);
305 turn.replaying = YES;
307 if( ! [self applyMoveString: move] ) {
308 _currentTurnNo = oldTurnNo;
310 NSLog(@"WARNING: %@ failed to apply stored move '%@'!", self,move);
318 NSLog(@"Reapplying state '%@'",state);
319 BeginDisableAnimations();
320 self.stateString = state;
321 EndDisableAnimations();
323 if( ! [self.stateString isEqual: state] ) {
324 _currentTurnNo = oldTurnNo;
326 NSLog(@"WARNING: %@ failed to apply stored state '%@'!", self,state);
330 _currentTurnNo = turnNo;
335 - (BOOL) animateMoveFrom: (CALayer<BitHolder>*)src to: (CALayer<BitHolder>*)dst
337 if( src==nil || dst==nil || dst==src )
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] )
344 ChangeSuperlayer(bit, _board.superlayer, -1);
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"
351 from: [NSValue valueWithCGPoint: bit.position]
352 to: [NSValue valueWithCGPoint: endPosition]
354 from: [NSValue valueWithPoint: NSPointFromCGPoint(bit.position)]
355 to: [NSValue valueWithPoint: NSPointFromCGPoint(endPosition)]
359 dst.highlighted = NO;
362 [src draggedBit: bit to: dst];
363 [self bit: bit movedFrom: src to: dst];
368 - (BOOL) animatePlacementIn: (CALayer<BitHolder>*)dst
372 Bit *bit = [self bitToPlaceInHolder: dst];
376 CALayer<BitHolder>* oldHolder = (CALayer<BitHolder>*) bit.holder;
378 if( oldHolder != dst )
379 return [self animateMoveFrom: oldHolder to: dst];
381 bit.position = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: _board.superlayer];
382 ChangeSuperlayer(bit, _board.superlayer, -1);
384 dst.highlighted = YES;
389 dst.highlighted = NO;
392 [self bit: bit movedFrom: nil to: dst];
398 #pragma mark GAMEPLAY METHODS TO BE OVERRIDDEN:
401 + (NSString*) identifier
403 NSString* name = [self description];
404 if( [name hasSuffix: @"Game"] )
405 name = [name substringToIndex: name.length-4];
409 + (NSString*) displayName
411 return [self identifier];
414 + (BOOL) landscapeOriented
420 - (NSString*) initialStateString
426 - (CGImageRef) iconForPlayer: (int)playerIndex
432 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src
437 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
442 - (void) bit: (Bit*)bit movedFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
447 - (Bit*) bitToPlaceInHolder: (id<BitHolder>)holder
453 - (BOOL) clickedBit: (Bit*)bit
458 - (Player*) checkForWinner
463 /* These are abstract
465 - (NSString*) stateString {return @"";}
466 - (void) setStateString: (NSString*)s { }
468 - (BOOL) applyMoveString: (NSString*)move {return NO;}
477 @implementation CALayer (Game)
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"];
488 NSAssert1(NO,@"Couldn't look up Game from %@",self);