* Fixed scaling of king pieces when the board's state is set.
* Added IBOutlets for tilting the BoardView.
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();
146 - (CGFloat) tablePerspectiveAngle
148 return _tablePerspectiveAngle;
151 - (void) setTablePerspectiveAngle: (CGFloat)angle
153 if( angle != _tablePerspectiveAngle ) {
154 _tablePerspectiveAngle = angle;
155 [self perspectiveChanged];
159 - (void) perspectiveChanged
165 #pragma mark PLAYERS:
168 - (void) setNumberOfPlayers: (unsigned)n
170 NSMutableArray *players = [NSMutableArray arrayWithCapacity: n];
171 for( int i=1; i<=n; i++ ) {
172 Player *player = [[Player alloc] initWithGame: self];
173 player.name = [NSString stringWithFormat: @"Player %i",i];
174 [players addObject: player];
177 self.players = players;
180 Turn *turn = [[Turn alloc] initStartOfGame: self];
181 setObj(&_turns, [NSMutableArray arrayWithObject: turn]);
186 - (Player*) remotePlayer
188 for( Player *player in _players )
196 return self.remotePlayer == nil;
199 - (Player*) currentPlayer
201 return self.currentTurn.player;
204 + (NSArray*) keyPathsForValuesAffectingCurrentPlayer {return [NSArray arrayWithObject: @"currentTurn"];}
211 - (Turn*) currentTurn
213 return [_turns objectAtIndex: _currentTurnNo];
218 return [_turns lastObject];
221 + (NSArray*) keyPathsForValuesAffectingCurrentTurn {return [NSArray arrayWithObject: @"currentTurnNo"];}
222 + (NSArray*) keyPathsForValuesAffectingLatestTurn {return [NSArray arrayWithObject: @"turns"];}
227 Turn *lastTurn = [_turns lastObject];
228 NSAssert(lastTurn.status==kTurnFinished,@"Can't _startTurn till previous turn is finished");
229 Turn *newTurn = [[Turn alloc] initWithPlayer: lastTurn.nextPlayer];
231 [self willChangeValueForKey: @"turns"];
232 [_turns addObject: newTurn];
233 [self willChangeValueForKey: @"turns"];
235 self.currentTurnNo = _turns.count-1;
241 Turn *latest = self.latestTurn;
242 if( latest.player.local && latest.status < kTurnComplete ) {
243 // Automatically skip from latest finished turn, since board state is the same:
244 unsigned latestTurnNo = self.maxTurnNo;
245 if( _currentTurnNo==latestTurnNo-1 ) {
246 NSLog(@"okToMove: skipping from turn %i to %i",_currentTurnNo,latestTurnNo);
247 self.currentTurnNo = latestTurnNo;
249 if( _currentTurnNo==latestTurnNo )
258 Turn *curTurn = self.currentTurn;
259 if( curTurn.isLatestTurn && ! curTurn.replaying ) {
260 curTurn.status = kTurnComplete;
261 NSLog(@"--- End of %@", curTurn);
263 Player *winner = [self checkForWinner];
265 NSLog(@"*** The %@ Ends! The winner is %@ ! ***", self.class, winner);
266 self.winner = winner;
269 if( ! _requireConfirmation || !curTurn.player.local )
270 [self confirmCurrentTurn];
272 [[NSNotificationCenter defaultCenter] postNotificationName: kTurnCompleteNotification
277 - (void) cancelCurrentTurn
279 Turn *curTurn = self.currentTurn;
280 if( curTurn.status > kTurnEmpty && curTurn.status < kTurnFinished ) {
284 self.stateString = curTurn.previousTurn.boardState;
285 curTurn.status = kTurnEmpty;
289 - (void) confirmCurrentTurn
291 Turn *curTurn = self.currentTurn;
292 if( curTurn.status == kTurnComplete ) {
293 curTurn.status = kTurnFinished;
300 - (BOOL) isLatestTurn
302 return _currentTurnNo == _turns.count-1;
305 - (unsigned) maxTurnNo
307 return _turns.count-1;
310 + (NSArray*) keyPathsForValuesAffectingIsLatestTurn {return [NSArray arrayWithObjects: @"currentTurnNo",@"turns",nil];}
311 + (NSArray*) keyPathsForValuesAffectingMaxTurnNo {return [NSArray arrayWithObjects: @"turns",nil];}
313 - (unsigned) currentTurnNo
315 return _currentTurnNo;
320 #pragma mark REPLAYING TURNS:
323 - (void) setCurrentTurnNo: (unsigned)turnNo
325 NSParameterAssert(turnNo<=self.maxTurnNo);
326 unsigned oldTurnNo = _currentTurnNo;
327 if( turnNo != oldTurnNo ) {
329 Turn *turn = [_turns objectAtIndex: turnNo];
331 if( turn.status == kTurnEmpty )
332 state = turn.previousTurn.boardState;
334 state = turn.boardState;
335 NSAssert1(state,@"empty boardState at turn #%i",turnNo);
336 _currentTurnNo = turnNo;
337 if( turnNo==oldTurnNo+1 ) {
338 NSString *move = turn.move;
340 NSLog(@"Reapplying move '%@'",move);
341 turn.replaying = YES;
343 if( ! [self applyMoveString: move] ) {
344 _currentTurnNo = oldTurnNo;
345 Warn(@"%@ failed to apply stored move '%@'!", self,move);
353 NSLog(@"Reapplying state '%@'",state);
354 BeginDisableAnimations();
355 self.stateString = state;
356 EndDisableAnimations();
358 if( ! [self.stateString isEqual: state] ) {
359 _currentTurnNo = oldTurnNo;
360 Warn(@"%@ failed to apply stored state '%@'!", self,state);
364 _currentTurnNo = turnNo;
369 - (BOOL) animateMoveFrom: (CALayer<BitHolder>*)src to: (CALayer<BitHolder>*)dst
371 if( src==nil || dst==nil || dst==src )
373 Bit *bit = [src canDragBit: src.bit];
374 if( ! bit || ! [dst canDropBit: bit atPoint: GetCGRectCenter(dst.bounds)]
375 || ! [self canBit: bit moveFrom: src to: dst] )
378 ChangeSuperlayer(bit, _table.superlayer, -1);
380 dst.highlighted = YES;
381 [bit performSelector: @selector(setPickedUp:) withObject:nil afterDelay: 0.15];
382 CGPoint endPosition = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: bit.superlayer];
383 [bit animateAndBlock: @"position"
385 from: [NSValue valueWithCGPoint: bit.position]
386 to: [NSValue valueWithCGPoint: endPosition]
388 from: [NSValue valueWithPoint: NSPointFromCGPoint(bit.position)]
389 to: [NSValue valueWithPoint: NSPointFromCGPoint(endPosition)]
393 dst.highlighted = NO;
396 [src draggedBit: bit to: dst];
397 [self bit: bit movedFrom: src to: dst];
402 - (BOOL) animatePlacementIn: (CALayer<BitHolder>*)dst
406 Bit *bit = [self bitToPlaceInHolder: dst];
410 CALayer<BitHolder>* oldHolder = (CALayer<BitHolder>*) bit.holder;
412 if( oldHolder != dst )
413 return [self animateMoveFrom: oldHolder to: dst];
415 bit.position = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: _table.superlayer];
416 ChangeSuperlayer(bit, _table.superlayer, -1);
418 dst.highlighted = YES;
423 dst.highlighted = NO;
426 [self bit: bit movedFrom: nil to: dst];
432 #pragma mark GAMEPLAY METHODS TO BE OVERRIDDEN:
435 + (NSString*) identifier
437 NSString* name = [self description];
438 if( [name hasSuffix: @"Game"] )
439 name = [name substringToIndex: name.length-4];
443 + (NSString*) displayName
445 return [self identifier];
448 + (BOOL) landscapeOriented
454 - (NSString*) initialStateString
460 - (CGImageRef) iconForPlayer: (int)playerIndex
466 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src
471 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
476 - (void) bit: (Bit*)bit movedFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
481 - (Bit*) bitToPlaceInHolder: (id<BitHolder>)holder
487 - (BOOL) clickedBit: (Bit*)bit
492 - (Player*) checkForWinner
497 /* These are abstract
499 - (NSString*) stateString {return @"";}
500 - (void) setStateString: (NSString*)s { }
502 - (BOOL) applyMoveString: (NSString*)move {return NO;}
511 @implementation CALayer (Game)
515 // The Game object stores a pointer to itself as the value of the "Game" property
516 // of its root layer. (CALayers can have arbitrary KV properties stored into them.)
517 for( CALayer *layer = self; layer; layer=layer.superlayer ) {
518 Game *game = [layer valueForKey: @"Game"];
522 NSAssert1(NO,@"Couldn't look up Game from %@",self);