jens@0: /* This code is based on Apple's "GeekGameBoard" sample code, version 1.0. jens@0: http://developer.apple.com/samplecode/GeekGameBoard/ jens@0: Copyright © 2007 Apple Inc. Copyright © 2008 Jens Alfke. All Rights Reserved. jens@0: jens@0: Redistribution and use in source and binary forms, with or without modification, are permitted jens@0: provided that the following conditions are met: jens@0: jens@0: * Redistributions of source code must retain the above copyright notice, this list of conditions jens@0: and the following disclaimer. jens@0: * Redistributions in binary form must reproduce the above copyright notice, this list of jens@0: conditions and the following disclaimer in the documentation and/or other materials provided jens@0: with the distribution. jens@0: jens@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR jens@0: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND jens@0: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- jens@0: BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES jens@0: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR jens@0: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN jens@0: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF jens@0: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jens@0: */ jens@10: #import "Game+Protected.h" jens@7: #import "QuartzUtils.h" jens@10: #import "GGBUtils.h" jens@0: jens@0: jens@0: @interface Game () jens@0: @property (copy) NSArray *players; jens@10: @property (assign) Player *winner; jens@10: - (void) _startTurn; jens@0: @end jens@0: jens@0: jens@0: @implementation Game jens@0: jens@0: jens@10: - (id) init jens@0: { jens@8: self = [super init]; jens@8: if (self != nil) { jens@10: // Don't create _turns till -initWithCoder or -setNumberOfPlayers:. jens@8: } jens@8: return self; jens@8: } jens@8: jens@10: jens@10: - (id) initWithCoder: (NSCoder*)decoder jens@8: { jens@10: self = [self init]; jens@10: if( self ) { jens@10: _players = [[decoder decodeObjectForKey: @"players"] mutableCopy]; jens@10: _winner = [decoder decodeObjectForKey: @"winner"]; jens@10: _turns = [[decoder decodeObjectForKey: @"turns"] mutableCopy]; jens@10: _extraValues = [[decoder decodeObjectForKey: @"extraValues"] mutableCopy]; jens@10: self.currentTurnNo = self.maxTurnNo; jens@10: } jens@8: return self; jens@8: } jens@8: jens@10: jens@10: - (void) encodeWithCoder: (NSCoder*)coder jens@10: { jens@10: [coder encodeObject: _players forKey: @"players"]; jens@10: [coder encodeObject: _winner forKey: @"winner"]; jens@10: [coder encodeObject: _turns forKey: @"turns"]; jens@10: [coder encodeObject: _extraValues forKey: @"extraValues"]; jens@10: } jens@10: jens@10: jens@16: - (id) initNewGameWithTable: (GGBLayer*)board jens@0: { jens@8: self = [self init]; jens@10: if( self ) { jens@16: self.table = board; jens@10: NSAssert1(_players && _turns, @"%@ failed to set numberOfPlayers",self); jens@0: } jens@0: return self; jens@0: } jens@0: jens@0: jens@0: - (void) dealloc jens@0: { jens@16: [_table release]; jens@0: [_players release]; jens@10: [_turns release]; jens@10: [_extraValues release]; jens@0: [super dealloc]; jens@0: } jens@0: jens@0: jens@10: @synthesize players=_players, winner=_winner, turns=_turns, requireConfirmation=_requireConfirmation; jens@10: jens@10: jens@10: - (id)valueForUndefinedKey:(NSString *)key jens@10: { jens@10: return [_extraValues objectForKey: key]; jens@10: } jens@10: jens@10: - (void)setValue:(id)value forUndefinedKey:(NSString *)key jens@10: { jens@10: if( ! _extraValues ) jens@10: _extraValues = [[NSMutableDictionary alloc] init]; jens@10: if( value ) jens@10: [_extraValues setObject: value forKey: key]; jens@10: else jens@10: [_extraValues removeObjectForKey: key]; jens@10: } jens@10: jens@10: jens@10: #pragma mark - jens@10: #pragma mark BOARD: jens@10: jens@10: jens@10: - (void) setUpBoard jens@10: { jens@10: NSAssert1(NO,@"%@ forgot to implement -setUpBoard",[self class]); jens@10: } jens@10: jens@16: - (GGBLayer*) table jens@10: { jens@16: return _table; jens@10: } jens@10: jens@16: - (void) setTable: (GGBLayer*)board jens@10: { jens@16: setObj(&_table,board); jens@14: if( board ) { jens@14: // Store a pointer to myself as the value of the "Game" property jens@14: // of my root layer. (CALayers can have arbitrary KV properties stored into them.) jens@14: // This is used by the -[CALayer game] category method defined below, to find the Game. jens@16: [_table setValue: self forKey: @"Game"]; jens@14: jens@14: BeginDisableAnimations(); jens@14: jens@14: // Tell the game to add the necessary bits to the board: jens@14: [self setUpBoard]; jens@14: jens@14: // Re-apply the current state to set up the pieces/cards: jens@14: self.stateString = [[_turns objectAtIndex: _currentTurnNo] boardState]; jens@14: jens@14: EndDisableAnimations(); jens@14: } jens@10: } jens@10: jens@22: - (CGFloat) tablePerspectiveAngle jens@22: { jens@22: return _tablePerspectiveAngle; jens@22: } jens@22: jens@22: - (void) setTablePerspectiveAngle: (CGFloat)angle jens@22: { jens@22: if( angle != _tablePerspectiveAngle ) { jens@22: _tablePerspectiveAngle = angle; jens@22: [self perspectiveChanged]; jens@22: } jens@22: } jens@22: jens@22: - (void) perspectiveChanged jens@22: { jens@22: } jens@22: jens@10: jens@10: #pragma mark - jens@10: #pragma mark PLAYERS: jens@0: jens@0: jens@0: - (void) setNumberOfPlayers: (unsigned)n jens@0: { jens@0: NSMutableArray *players = [NSMutableArray arrayWithCapacity: n]; jens@0: for( int i=1; i<=n; i++ ) { jens@0: Player *player = [[Player alloc] initWithGame: self]; jens@0: player.name = [NSString stringWithFormat: @"Player %i",i]; jens@0: [players addObject: player]; jens@0: [player release]; jens@0: } jens@10: self.players = players; jens@0: self.winner = nil; jens@10: jens@10: Turn *turn = [[Turn alloc] initStartOfGame: self]; jens@10: setObj(&_turns, [NSMutableArray arrayWithObject: turn]); jens@10: [turn release]; jens@10: [self _startTurn]; jens@0: } jens@0: jens@10: - (Player*) remotePlayer jens@7: { jens@10: for( Player *player in _players ) jens@10: if( ! player.local ) jens@10: return player; jens@10: return nil; jens@7: } jens@7: jens@10: - (BOOL) isLocal jens@7: { jens@10: return self.remotePlayer == nil; jens@7: } jens@7: jens@10: - (Player*) currentPlayer jens@10: { jens@10: return self.currentTurn.player; jens@10: } jens@7: jens@10: + (NSArray*) keyPathsForValuesAffectingCurrentPlayer {return [NSArray arrayWithObject: @"currentTurn"];} jens@10: jens@10: jens@10: #pragma mark - jens@10: #pragma mark TURNS: jens@10: jens@10: jens@10: - (Turn*) currentTurn jens@0: { jens@10: return [_turns objectAtIndex: _currentTurnNo]; jens@10: } jens@10: jens@10: - (Turn*) latestTurn jens@10: { jens@10: return [_turns lastObject]; jens@10: } jens@10: jens@10: + (NSArray*) keyPathsForValuesAffectingCurrentTurn {return [NSArray arrayWithObject: @"currentTurnNo"];} jens@10: + (NSArray*) keyPathsForValuesAffectingLatestTurn {return [NSArray arrayWithObject: @"turns"];} jens@10: jens@10: jens@10: - (void) _startTurn jens@10: { jens@10: Turn *lastTurn = [_turns lastObject]; jens@10: NSAssert(lastTurn.status==kTurnFinished,@"Can't _startTurn till previous turn is finished"); jens@10: Turn *newTurn = [[Turn alloc] initWithPlayer: lastTurn.nextPlayer]; jens@10: jens@10: [self willChangeValueForKey: @"turns"]; jens@10: [_turns addObject: newTurn]; jens@10: [self willChangeValueForKey: @"turns"]; jens@10: [newTurn release]; jens@10: self.currentTurnNo = _turns.count-1; jens@0: } jens@0: jens@0: jens@15: - (BOOL) okToMove jens@15: { jens@15: Turn *latest = self.latestTurn; jens@15: if( latest.player.local && latest.status < kTurnComplete ) { jens@15: // Automatically skip from latest finished turn, since board state is the same: jens@15: unsigned latestTurnNo = self.maxTurnNo; jens@15: if( _currentTurnNo==latestTurnNo-1 ) { jens@15: NSLog(@"okToMove: skipping from turn %i to %i",_currentTurnNo,latestTurnNo); jens@15: self.currentTurnNo = latestTurnNo; jens@15: } jens@15: if( _currentTurnNo==latestTurnNo ) jens@15: return YES; jens@15: } jens@15: return NO; jens@15: } jens@15: jens@15: jens@0: - (void) endTurn jens@0: { jens@10: Turn *curTurn = self.currentTurn; jens@10: if( curTurn.isLatestTurn && ! curTurn.replaying ) { jens@10: curTurn.status = kTurnComplete; jens@10: NSLog(@"--- End of %@", curTurn); jens@10: jens@10: Player *winner = [self checkForWinner]; jens@10: if( winner ) { jens@10: NSLog(@"*** The %@ Ends! The winner is %@ ! ***", self.class, winner); jens@10: self.winner = winner; jens@10: } jens@10: jens@10: if( ! _requireConfirmation || !curTurn.player.local ) jens@10: [self confirmCurrentTurn]; jens@7: jens@10: [[NSNotificationCenter defaultCenter] postNotificationName: kTurnCompleteNotification jens@10: object: curTurn]; jens@7: } jens@7: } jens@7: jens@10: - (void) cancelCurrentTurn jens@10: { jens@10: Turn *curTurn = self.currentTurn; jens@10: if( curTurn.status > kTurnEmpty && curTurn.status < kTurnFinished ) { jens@10: if( _winner ) jens@10: self.winner = nil; jens@16: if( _table ) jens@10: self.stateString = curTurn.previousTurn.boardState; jens@10: curTurn.status = kTurnEmpty; jens@10: } jens@10: } jens@10: jens@10: - (void) confirmCurrentTurn jens@10: { jens@10: Turn *curTurn = self.currentTurn; jens@10: if( curTurn.status == kTurnComplete ) { jens@10: curTurn.status = kTurnFinished; jens@10: if( ! _winner ) jens@10: [self _startTurn]; jens@10: } jens@10: } jens@10: jens@7: jens@7: - (BOOL) isLatestTurn jens@7: { jens@10: return _currentTurnNo == _turns.count-1; jens@7: } jens@7: jens@10: - (unsigned) maxTurnNo jens@10: { jens@10: return _turns.count-1; jens@10: } jens@7: jens@10: + (NSArray*) keyPathsForValuesAffectingIsLatestTurn {return [NSArray arrayWithObjects: @"currentTurnNo",@"turns",nil];} jens@10: + (NSArray*) keyPathsForValuesAffectingMaxTurnNo {return [NSArray arrayWithObjects: @"turns",nil];} jens@10: jens@10: - (unsigned) currentTurnNo jens@10: { jens@10: return _currentTurnNo; jens@10: } jens@10: jens@10: jens@10: #pragma mark - jens@10: #pragma mark REPLAYING TURNS: jens@10: jens@10: jens@10: - (void) setCurrentTurnNo: (unsigned)turnNo jens@10: { jens@10: NSParameterAssert(turnNo<=self.maxTurnNo); jens@10: unsigned oldTurnNo = _currentTurnNo; jens@10: if( turnNo != oldTurnNo ) { jens@16: if( _table ) { jens@10: Turn *turn = [_turns objectAtIndex: turnNo]; jens@10: NSString *state; jens@10: if( turn.status == kTurnEmpty ) jens@10: state = turn.previousTurn.boardState; jens@10: else jens@10: state = turn.boardState; jens@10: NSAssert1(state,@"empty boardState at turn #%i",turnNo); jens@10: _currentTurnNo = turnNo; jens@10: if( turnNo==oldTurnNo+1 ) { jens@10: NSString *move = turn.move; jens@10: if( move ) { jens@10: NSLog(@"Reapplying move '%@'",move); jens@10: turn.replaying = YES; jens@10: @try{ jens@10: if( ! [self applyMoveString: move] ) { jens@10: _currentTurnNo = oldTurnNo; jens@21: Warn(@"%@ failed to apply stored move '%@'!", self,move); jens@10: return; jens@10: } jens@10: }@finally{ jens@10: turn.replaying = NO; jens@10: } jens@10: } jens@10: } else { jens@10: NSLog(@"Reapplying state '%@'",state); jens@10: BeginDisableAnimations(); jens@10: self.stateString = state; jens@10: EndDisableAnimations(); jens@10: } jens@10: if( ! [self.stateString isEqual: state] ) { jens@10: _currentTurnNo = oldTurnNo; jens@21: Warn(@"%@ failed to apply stored state '%@'!", self,state); jens@10: return; jens@10: } jens@10: } else jens@10: _currentTurnNo = turnNo; jens@10: } jens@10: } jens@10: jens@10: jens@10: - (BOOL) animateMoveFrom: (CALayer*)src to: (CALayer*)dst jens@7: { jens@7: if( src==nil || dst==nil || dst==src ) jens@7: return NO; jens@7: Bit *bit = [src canDragBit: src.bit]; jens@7: if( ! bit || ! [dst canDropBit: bit atPoint: GetCGRectCenter(dst.bounds)] jens@7: || ! [self canBit: bit moveFrom: src to: dst] ) jens@7: return NO; jens@7: jens@16: ChangeSuperlayer(bit, _table.superlayer, -1); jens@7: bit.pickedUp = YES; jens@7: dst.highlighted = YES; jens@7: [bit performSelector: @selector(setPickedUp:) withObject:nil afterDelay: 0.15]; jens@7: CGPoint endPosition = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: bit.superlayer]; jens@7: [bit animateAndBlock: @"position" jens@8: #if TARGET_OS_IPHONE jens@8: from: [NSValue valueWithCGPoint: bit.position] jens@8: to: [NSValue valueWithCGPoint: endPosition] jens@8: #else jens@7: from: [NSValue valueWithPoint: NSPointFromCGPoint(bit.position)] jens@7: to: [NSValue valueWithPoint: NSPointFromCGPoint(endPosition)] jens@8: #endif jens@7: duration: 0.25]; jens@7: dst.bit = bit; jens@7: dst.highlighted = NO; jens@7: bit.pickedUp = NO; jens@7: jens@7: [src draggedBit: bit to: dst]; jens@7: [self bit: bit movedFrom: src to: dst]; jens@10: return YES; jens@10: } jens@10: jens@10: jens@10: - (BOOL) animatePlacementIn: (CALayer*)dst jens@10: { jens@10: if( dst == nil ) jens@10: return NO; jens@10: Bit *bit = [self bitToPlaceInHolder: dst]; jens@10: if( ! bit ) jens@10: return NO; jens@10: jens@10: CALayer* oldHolder = (CALayer*) bit.holder; jens@10: if( oldHolder ) { jens@10: if( oldHolder != dst ) jens@10: return [self animateMoveFrom: oldHolder to: dst]; jens@10: } else jens@16: bit.position = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: _table.superlayer]; jens@16: ChangeSuperlayer(bit, _table.superlayer, -1); jens@10: bit.pickedUp = YES; jens@10: dst.highlighted = YES; jens@10: jens@10: DelayFor(0.2); jens@10: jens@10: dst.bit = bit; jens@10: dst.highlighted = NO; jens@10: bit.pickedUp = NO; jens@10: jens@10: [self bit: bit movedFrom: nil to: dst]; jens@7: return YES; jens@7: } jens@7: jens@7: jens@0: #pragma mark - jens@0: #pragma mark GAMEPLAY METHODS TO BE OVERRIDDEN: jens@0: jens@0: jens@10: + (NSString*) identifier jens@10: { jens@10: NSString* name = [self description]; jens@10: if( [name hasSuffix: @"Game"] ) jens@10: name = [name substringToIndex: name.length-4]; jens@10: return name; jens@10: } jens@10: jens@10: + (NSString*) displayName jens@10: { jens@10: return [self identifier]; jens@10: } jens@10: jens@4: + (BOOL) landscapeOriented jens@4: { jens@4: return NO; jens@4: } jens@4: jens@4: jens@10: - (NSString*) initialStateString jens@10: { jens@10: return @""; jens@10: } jens@10: jens@10: jens@10: - (CGImageRef) iconForPlayer: (int)playerIndex jens@10: { jens@10: return nil; jens@10: } jens@10: jens@10: jens@0: - (BOOL) canBit: (Bit*)bit moveFrom: (id)src jens@0: { jens@0: return YES; jens@0: } jens@0: jens@0: - (BOOL) canBit: (Bit*)bit moveFrom: (id)src to: (id)dst jens@0: { jens@0: return YES; jens@0: } jens@0: jens@0: - (void) bit: (Bit*)bit movedFrom: (id)src to: (id)dst jens@0: { jens@0: [self endTurn]; jens@0: } jens@0: jens@3: - (Bit*) bitToPlaceInHolder: (id)holder jens@3: { jens@3: return nil; jens@3: } jens@3: jens@3: jens@0: - (BOOL) clickedBit: (Bit*)bit jens@0: { jens@0: return YES; jens@0: } jens@0: jens@0: - (Player*) checkForWinner jens@0: { jens@0: return nil; jens@0: } jens@0: jens@10: /* These are abstract jens@10: jens@7: - (NSString*) stateString {return @"";} jens@7: - (void) setStateString: (NSString*)s { } jens@7: jens@7: - (BOOL) applyMoveString: (NSString*)move {return NO;} jens@10: */ jens@7: jens@0: @end jens@0: jens@0: jens@0: jens@0: jens@10: #pragma mark - jens@0: @implementation CALayer (Game) jens@0: jens@0: - (Game*) game jens@0: { jens@0: // The Game object stores a pointer to itself as the value of the "Game" property jens@0: // of its root layer. (CALayers can have arbitrary KV properties stored into them.) jens@0: for( CALayer *layer = self; layer; layer=layer.superlayer ) { jens@0: Game *game = [layer valueForKey: @"Game"]; jens@0: if( game ) jens@0: return game; jens@0: } jens@0: NSAssert1(NO,@"Couldn't look up Game from %@",self); jens@0: return nil; jens@0: } jens@0: jens@0: @end