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@0: #import "KlondikeGame.h" jens@0: #import "Deck.h" jens@0: #import "PlayingCard.h" jens@0: #import "Stack.h" jens@4: #import "QuartzUtils.h" jens@0: jens@0: jens@0: #define kStackHeight 500 jens@0: jens@0: jens@4: @implementation KlondikeGame jens@0: jens@0: jens@4: + (BOOL) landscapeOriented jens@4: { jens@4: return YES; jens@4: } jens@0: jens@0: jens@10: - (id) init jens@0: { jens@10: self = [super init]; jens@10: if (self != nil) jens@0: [self setNumberOfPlayers: 1]; jens@10: return self; jens@10: } jens@10: jens@0: jens@10: - (void) setUpBoard jens@10: { jens@10: CGSize boardSize = _board.bounds.size; jens@10: CGFloat xSpacing = floor(boardSize.width/7); jens@10: CGSize kCardSize; jens@10: kCardSize.width = round(xSpacing * 0.9); // 1/7th of width, with 10% gap jens@10: kCardSize.height = round(kCardSize.width * 1.5); jens@10: CGFloat gap = xSpacing-kCardSize.width; jens@10: [Card setCardSize: kCardSize]; jens@10: jens@10: CGPoint pos = {floor(gap/2)+kCardSize.width/2, floor(boardSize.height-kCardSize.height/2)}; jens@13: _deck = [[[Deck alloc] initWithCardsOfClass: [PlayingCard class]] autorelease]; jens@10: [_deck shuffle]; jens@10: _deck.position = pos; jens@10: [_board addSublayer: _deck]; jens@10: jens@10: pos.x += xSpacing; jens@13: _sink = [[[Deck alloc] init] autorelease]; jens@10: _sink.position = pos; jens@10: [_board addSublayer: _sink]; jens@10: jens@10: pos.x += xSpacing; jens@10: for( CardSuit suit=kSuitClubs; suit<=kSuitSpades; suit++ ) { jens@10: pos.x += xSpacing; jens@13: Deck *aces = [[[Deck alloc] init] autorelease]; jens@10: aces.position = pos; jens@10: [_board addSublayer: aces]; jens@10: _aces[suit] = aces; jens@10: } jens@10: jens@10: CGRect stackFrame = {{floor(gap/2), gap}, jens@10: {kCardSize.width, boardSize.height-kCardSize.height-2*gap}}; jens@10: CGPoint startPos = CGPointMake(kCardSize.width/2,kCardSize.height/2); jens@10: CGSize spacing = {0, floor((stackFrame.size.height-kCardSize.height)/11.0)}; jens@10: for( int s=0; s<7; s++ ) { jens@10: Stack *stack = [[Stack alloc] initWithStartPos: startPos spacing: spacing]; jens@10: stack.frame = stackFrame; jens@10: stackFrame.origin.x += xSpacing; jens@10: stack.backgroundColor = nil; //kAlmostInvisibleWhiteColor; jens@10: stack.dragAsStacks = YES; jens@10: [_board addSublayer: stack]; jens@4: jens@10: // According to the rules, one card should be added to each stack in turn, instead jens@10: // of populating entire stacks one at a time. However, if one trusts the Deck's jens@10: // -shuffle method (which uses the random() function, seeded with a high-entropy jens@10: // cryptographically-strong value), it shouldn't make any difference :-) jens@10: for( int c=0; c<=s; c++ ) jens@10: [stack addBit: [_deck removeTopCard]]; jens@10: ((Card*)stack.bits.lastObject).faceUp = YES; jens@0: } jens@0: } jens@0: jens@0: jens@0: - (BOOL) clickedBit: (Bit*)bit jens@0: { jens@0: if( [bit isKindOfClass: [Card class]] ) { jens@0: Card *card = (Card*)bit; jens@0: if( card.holder == _deck ) { jens@0: // Click on deck deals 3 cards to the sink: jens@0: for( int i=0; i<3; i++ ) { jens@0: Card *card = [_deck removeTopCard]; jens@0: if( card ) { jens@0: [_sink addCard: card]; jens@0: card.faceUp = YES; jens@0: } jens@0: } jens@0: [self endTurn]; jens@0: return YES; jens@0: } else if( card.holder == _sink ) { jens@0: // Clicking the sink when the deck is empty re-deals: jens@0: if( _deck.empty ) { jens@0: [_deck addCards: [_sink removeAllCards]]; jens@0: [_deck flip]; jens@0: [self endTurn]; jens@0: return YES; jens@0: } jens@0: } else { jens@0: // Click on a card elsewhere turns it face-up: jens@0: if( ! card.faceUp ) { jens@0: card.faceUp = YES; jens@0: return YES; jens@0: } jens@0: } jens@0: } jens@0: return NO; jens@0: } jens@0: jens@0: jens@0: - (BOOL) canBit: (Bit*)bit moveFrom: (id)src jens@0: { jens@0: if( [bit isKindOfClass: [DraggedStack class]] ) { jens@0: Card *bottomSrc = [[(DraggedStack*)bit bits] objectAtIndex: 0]; jens@0: if( ! bottomSrc.faceUp ) jens@0: return NO; jens@0: } jens@0: return YES; jens@0: } jens@0: jens@0: jens@0: - (BOOL) canBit: (Bit*)bit moveFrom: (id)src to: (id)dst jens@0: { jens@0: if( src==_deck || dst==_deck || dst==_sink ) jens@0: return NO; jens@0: jens@0: // Find the bottom card being moved, and the top card it's moving onto: jens@0: PlayingCard *bottomSrc; jens@0: if( [bit isKindOfClass: [DraggedStack class]] ) jens@0: bottomSrc = [[(DraggedStack*)bit bits] objectAtIndex: 0]; jens@0: else jens@0: bottomSrc = (PlayingCard*)bit; jens@0: jens@0: PlayingCard *topDst; jens@0: if( [dst isKindOfClass: [Deck class]] ) { jens@0: // Dragging to an ace pile: jens@0: if( ! [bit isKindOfClass: [Card class]] ) jens@0: return NO; jens@0: topDst = (PlayingCard*) ((Deck*)dst).topCard; jens@0: if( topDst == nil ) jens@0: return bottomSrc.rank == kRankAce; jens@0: else jens@0: return bottomSrc.suit == topDst.suit && bottomSrc.rank == topDst.rank+1; jens@0: jens@0: } else { jens@0: // Dragging to a card stack: jens@0: topDst = (PlayingCard*) ((Stack*)dst).topBit; jens@0: if( topDst == nil ) jens@0: return bottomSrc.rank == kRankKing; jens@0: else jens@0: return bottomSrc.color != topDst.color && bottomSrc.rank == topDst.rank-1; jens@0: } jens@0: } jens@0: jens@0: jens@0: - (Player*) checkForWinner jens@0: { jens@0: for( CardSuit suit=kSuitClubs; suit<=kSuitSpades; suit++ ) jens@0: if( _aces[suit].cards.count < 13 ) jens@0: return nil; jens@10: return self.currentPlayer; jens@0: } jens@0: jens@0: jens@0: jens@0: @end