More tweaks, including a "reversed" property for Grids to show the second player's perspective without turning the pieces upside-down.
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 *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;
329 NSLog(@"WARNING: %@ failed to apply stored move '%@'!", self,move);
337 NSLog(@"Reapplying state '%@'",state);
338 BeginDisableAnimations();
339 self.stateString = state;
340 EndDisableAnimations();
342 if( ! [self.stateString isEqual: state] ) {
343 _currentTurnNo = oldTurnNo;
345 NSLog(@"WARNING: %@ failed to apply stored state '%@'!", self,state);
349 _currentTurnNo = turnNo;
354 - (BOOL) animateMoveFrom: (CALayer<BitHolder>*)src to: (CALayer<BitHolder>*)dst
356 if( src==nil || dst==nil || dst==src )
358 Bit *bit = [src canDragBit: src.bit];
359 if( ! bit || ! [dst canDropBit: bit atPoint: GetCGRectCenter(dst.bounds)]
360 || ! [self canBit: bit moveFrom: src to: dst] )
363 ChangeSuperlayer(bit, _board.superlayer, -1);
365 dst.highlighted = YES;
366 [bit performSelector: @selector(setPickedUp:) withObject:nil afterDelay: 0.15];
367 CGPoint endPosition = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: bit.superlayer];
368 [bit animateAndBlock: @"position"
370 from: [NSValue valueWithCGPoint: bit.position]
371 to: [NSValue valueWithCGPoint: endPosition]
373 from: [NSValue valueWithPoint: NSPointFromCGPoint(bit.position)]
374 to: [NSValue valueWithPoint: NSPointFromCGPoint(endPosition)]
378 dst.highlighted = NO;
381 [src draggedBit: bit to: dst];
382 [self bit: bit movedFrom: src to: dst];
387 - (BOOL) animatePlacementIn: (CALayer<BitHolder>*)dst
391 Bit *bit = [self bitToPlaceInHolder: dst];
395 CALayer<BitHolder>* oldHolder = (CALayer<BitHolder>*) bit.holder;
397 if( oldHolder != dst )
398 return [self animateMoveFrom: oldHolder to: dst];
400 bit.position = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: _board.superlayer];
401 ChangeSuperlayer(bit, _board.superlayer, -1);
403 dst.highlighted = YES;
408 dst.highlighted = NO;
411 [self bit: bit movedFrom: nil to: dst];
417 #pragma mark GAMEPLAY METHODS TO BE OVERRIDDEN:
420 + (NSString*) identifier
422 NSString* name = [self description];
423 if( [name hasSuffix: @"Game"] )
424 name = [name substringToIndex: name.length-4];
428 + (NSString*) displayName
430 return [self identifier];
433 + (BOOL) landscapeOriented
439 - (NSString*) initialStateString
445 - (CGImageRef) iconForPlayer: (int)playerIndex
451 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src
456 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
461 - (void) bit: (Bit*)bit movedFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
466 - (Bit*) bitToPlaceInHolder: (id<BitHolder>)holder
472 - (BOOL) clickedBit: (Bit*)bit
477 - (Player*) checkForWinner
482 /* These are abstract
484 - (NSString*) stateString {return @"";}
485 - (void) setStateString: (NSString*)s { }
487 - (BOOL) applyMoveString: (NSString*)move {return NO;}
496 @implementation CALayer (Game)
500 // The Game object stores a pointer to itself as the value of the "Game" property
501 // of its root layer. (CALayers can have arbitrary KV properties stored into them.)
502 for( CALayer *layer = self; layer; layer=layer.superlayer ) {
503 Game *game = [layer valueForKey: @"Game"];
507 NSAssert1(NO,@"Couldn't look up Game from %@",self);