Source/KlondikeGame.m
author Jens Alfke <jens@mooseyard.com>
Wed Jul 09 17:07:45 2008 -0700 (2008-07-09)
changeset 15 73f8c889f053
parent 10 6c78cc6bd7a6
child 16 28392c9a969f
permissions -rw-r--r--
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.
     4 
     5     Redistribution and use in source and binary forms, with or without modification, are permitted
     6     provided that the following conditions are met:
     7 
     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.
    13 
    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.
    22 */
    23 #import "KlondikeGame.h"
    24 #import "Deck.h"
    25 #import "PlayingCard.h"
    26 #import "Stack.h"
    27 #import "QuartzUtils.h"
    28 
    29 
    30 #define kStackHeight 500
    31 
    32 
    33 @implementation KlondikeGame
    34 
    35 
    36 + (BOOL) landscapeOriented
    37 {
    38     return YES;
    39 }
    40 
    41 
    42 - (id) init
    43 {
    44     self = [super init];
    45     if (self != nil)
    46         [self setNumberOfPlayers: 1];
    47     return self;
    48 }
    49 
    50         
    51 - (void) setUpBoard
    52 {
    53     CGSize boardSize = _board.bounds.size;
    54     CGFloat xSpacing = floor(boardSize.width/7);
    55     CGSize kCardSize;
    56     kCardSize.width  = round(xSpacing * 0.9);  // 1/7th of width, with 10% gap
    57     kCardSize.height = round(kCardSize.width * 1.5);
    58     CGFloat gap = xSpacing-kCardSize.width;
    59     [Card setCardSize: kCardSize];
    60     
    61     CGPoint pos = {floor(gap/2)+kCardSize.width/2, floor(boardSize.height-kCardSize.height/2)};
    62     _deck = [[[Deck alloc] initWithCardsOfClass: [PlayingCard class]] autorelease];
    63     [_deck shuffle];
    64     _deck.position = pos;
    65     [_board addSublayer: _deck];
    66     
    67     pos.x += xSpacing;
    68     _sink = [[[Deck alloc] init] autorelease];
    69     _sink.position = pos;
    70     [_board addSublayer: _sink];
    71     
    72     pos.x += xSpacing;
    73     for( CardSuit suit=kSuitClubs; suit<=kSuitSpades; suit++ ) {
    74         pos.x += xSpacing;
    75         Deck *aces = [[[Deck alloc] init] autorelease];
    76         aces.position = pos;
    77         [_board addSublayer: aces];
    78         _aces[suit] = aces;
    79     }
    80     
    81     CGRect stackFrame = {{floor(gap/2), gap}, 
    82                          {kCardSize.width, boardSize.height-kCardSize.height-2*gap}};
    83     CGPoint startPos = CGPointMake(kCardSize.width/2,kCardSize.height/2);
    84     CGSize spacing = {0, floor((stackFrame.size.height-kCardSize.height)/11.0)};
    85     for( int s=0; s<7; s++ ) {
    86         Stack *stack = [[Stack alloc] initWithStartPos: startPos spacing: spacing];
    87         stack.frame = stackFrame;
    88         stackFrame.origin.x += xSpacing;
    89         stack.backgroundColor = nil; //kAlmostInvisibleWhiteColor;
    90         stack.dragAsStacks = YES;
    91         [_board addSublayer: stack];
    92         
    93         // According to the rules, one card should be added to each stack in turn, instead
    94         // of populating entire stacks one at a time. However, if one trusts the Deck's
    95         // -shuffle method (which uses the random() function, seeded with a high-entropy
    96         // cryptographically-strong value), it shouldn't make any difference :-)
    97         for( int c=0; c<=s; c++ )
    98             [stack addBit: [_deck removeTopCard]];
    99         ((Card*)stack.bits.lastObject).faceUp = YES;
   100     }
   101 }
   102 
   103 
   104 - (BOOL) clickedBit: (Bit*)bit
   105 {
   106     if( [bit isKindOfClass: [Card class]] ) {
   107         Card *card = (Card*)bit;
   108         if( card.holder == _deck ) {
   109             // Click on deck deals 3 cards to the sink:
   110             for( int i=0; i<3; i++ ) {
   111                 Card *card = [_deck removeTopCard];
   112                 if( card ) {
   113                     [_sink addCard: card];
   114                     card.faceUp = YES;
   115                 }
   116             }
   117             [self endTurn];
   118             return YES;
   119         } else if( card.holder == _sink ) {
   120             // Clicking the sink when the deck is empty re-deals:
   121             if( _deck.empty ) {
   122                 [_deck addCards: [_sink removeAllCards]];
   123                 [_deck flip];
   124                 [self endTurn];
   125                 return YES;
   126             }
   127         } else {
   128             // Click on a card elsewhere turns it face-up:
   129             if( ! card.faceUp ) {
   130                 card.faceUp = YES;
   131                 return YES;
   132             }
   133         }
   134     }
   135     return NO;
   136 }
   137 
   138 
   139 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src
   140 {
   141     if( [bit isKindOfClass: [DraggedStack class]] ) {
   142         Card *bottomSrc = [[(DraggedStack*)bit bits] objectAtIndex: 0];
   143         if( ! bottomSrc.faceUp )
   144             return NO;
   145     }
   146     return YES;
   147 }
   148 
   149 
   150 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
   151 {
   152     if( src==_deck || dst==_deck || dst==_sink )
   153         return NO;
   154     
   155     // Find the bottom card being moved, and the top card it's moving onto:
   156     PlayingCard *bottomSrc;
   157     if( [bit isKindOfClass: [DraggedStack class]] )
   158         bottomSrc = [[(DraggedStack*)bit bits] objectAtIndex: 0];
   159     else
   160         bottomSrc = (PlayingCard*)bit;
   161     
   162     PlayingCard *topDst;
   163     if( [dst isKindOfClass: [Deck class]] ) {
   164         // Dragging to an ace pile:
   165         if( ! [bit isKindOfClass: [Card class]] )
   166             return NO;
   167         topDst = (PlayingCard*) ((Deck*)dst).topCard;
   168         if( topDst == nil )
   169             return bottomSrc.rank == kRankAce;
   170         else
   171             return bottomSrc.suit == topDst.suit && bottomSrc.rank == topDst.rank+1;
   172         
   173     } else {
   174         // Dragging to a card stack:
   175         topDst = (PlayingCard*) ((Stack*)dst).topBit;
   176         if( topDst == nil )
   177             return bottomSrc.rank == kRankKing;
   178         else
   179             return bottomSrc.color != topDst.color && bottomSrc.rank == topDst.rank-1;
   180     }
   181 }
   182 
   183 
   184 - (Player*) checkForWinner
   185 {
   186     for( CardSuit suit=kSuitClubs; suit<=kSuitSpades; suit++ )
   187         if( _aces[suit].cards.count < 13 )
   188             return nil;
   189     return self.currentPlayer;
   190 }
   191 
   192 
   193 
   194 @end