Source/KlondikeGame.m
author Jens Alfke <jens@mooseyard.com>
Sat Jul 05 17:46:43 2008 -0700 (2008-07-05)
changeset 11 436cbdf56810
parent 4 d781b00f3ed4
child 13 db7bb080c3d5
permissions -rw-r--r--
* Improved drag-and-drop (supports CandyBar)
* Fixed DiscPiece
* Inheritable layer styles
etc.
     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 release];
    63     _deck = [[Deck alloc] initWithCardsOfClass: [PlayingCard class]];
    64     [_deck shuffle];
    65     _deck.position = pos;
    66     [_board addSublayer: _deck];
    67     
    68     pos.x += xSpacing;
    69     [_sink release];
    70     _sink = [[Deck alloc] init];
    71     _sink.position = pos;
    72     [_board addSublayer: _sink];
    73     
    74     pos.x += xSpacing;
    75     for( CardSuit suit=kSuitClubs; suit<=kSuitSpades; suit++ ) {
    76         pos.x += xSpacing;
    77         Deck *aces = [[Deck alloc] init];
    78         aces.position = pos;
    79         [_board addSublayer: aces];
    80         [_aces[suit] release];
    81         _aces[suit] = aces;
    82     }
    83     
    84     CGRect stackFrame = {{floor(gap/2), gap}, 
    85                          {kCardSize.width, boardSize.height-kCardSize.height-2*gap}};
    86     CGPoint startPos = CGPointMake(kCardSize.width/2,kCardSize.height/2);
    87     CGSize spacing = {0, floor((stackFrame.size.height-kCardSize.height)/11.0)};
    88     for( int s=0; s<7; s++ ) {
    89         Stack *stack = [[Stack alloc] initWithStartPos: startPos spacing: spacing];
    90         stack.frame = stackFrame;
    91         stackFrame.origin.x += xSpacing;
    92         stack.backgroundColor = nil; //kAlmostInvisibleWhiteColor;
    93         stack.dragAsStacks = YES;
    94         [_board addSublayer: stack];
    95         
    96         // According to the rules, one card should be added to each stack in turn, instead
    97         // of populating entire stacks one at a time. However, if one trusts the Deck's
    98         // -shuffle method (which uses the random() function, seeded with a high-entropy
    99         // cryptographically-strong value), it shouldn't make any difference :-)
   100         for( int c=0; c<=s; c++ )
   101             [stack addBit: [_deck removeTopCard]];
   102         ((Card*)stack.bits.lastObject).faceUp = YES;
   103     }
   104 }
   105 
   106 
   107 - (BOOL) clickedBit: (Bit*)bit
   108 {
   109     if( [bit isKindOfClass: [Card class]] ) {
   110         Card *card = (Card*)bit;
   111         if( card.holder == _deck ) {
   112             // Click on deck deals 3 cards to the sink:
   113             for( int i=0; i<3; i++ ) {
   114                 Card *card = [_deck removeTopCard];
   115                 if( card ) {
   116                     [_sink addCard: card];
   117                     card.faceUp = YES;
   118                 }
   119             }
   120             [self endTurn];
   121             return YES;
   122         } else if( card.holder == _sink ) {
   123             // Clicking the sink when the deck is empty re-deals:
   124             if( _deck.empty ) {
   125                 [_deck addCards: [_sink removeAllCards]];
   126                 [_deck flip];
   127                 [self endTurn];
   128                 return YES;
   129             }
   130         } else {
   131             // Click on a card elsewhere turns it face-up:
   132             if( ! card.faceUp ) {
   133                 card.faceUp = YES;
   134                 return YES;
   135             }
   136         }
   137     }
   138     return NO;
   139 }
   140 
   141 
   142 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src
   143 {
   144     if( [bit isKindOfClass: [DraggedStack class]] ) {
   145         Card *bottomSrc = [[(DraggedStack*)bit bits] objectAtIndex: 0];
   146         if( ! bottomSrc.faceUp )
   147             return NO;
   148     }
   149     return YES;
   150 }
   151 
   152 
   153 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
   154 {
   155     if( src==_deck || dst==_deck || dst==_sink )
   156         return NO;
   157     
   158     // Find the bottom card being moved, and the top card it's moving onto:
   159     PlayingCard *bottomSrc;
   160     if( [bit isKindOfClass: [DraggedStack class]] )
   161         bottomSrc = [[(DraggedStack*)bit bits] objectAtIndex: 0];
   162     else
   163         bottomSrc = (PlayingCard*)bit;
   164     
   165     PlayingCard *topDst;
   166     if( [dst isKindOfClass: [Deck class]] ) {
   167         // Dragging to an ace pile:
   168         if( ! [bit isKindOfClass: [Card class]] )
   169             return NO;
   170         topDst = (PlayingCard*) ((Deck*)dst).topCard;
   171         if( topDst == nil )
   172             return bottomSrc.rank == kRankAce;
   173         else
   174             return bottomSrc.suit == topDst.suit && bottomSrc.rank == topDst.rank+1;
   175         
   176     } else {
   177         // Dragging to a card stack:
   178         topDst = (PlayingCard*) ((Stack*)dst).topBit;
   179         if( topDst == nil )
   180             return bottomSrc.rank == kRankKing;
   181         else
   182             return bottomSrc.color != topDst.color && bottomSrc.rank == topDst.rank-1;
   183     }
   184 }
   185 
   186 
   187 - (Player*) checkForWinner
   188 {
   189     for( CardSuit suit=kSuitClubs; suit<=kSuitSpades; suit++ )
   190         if( _aces[suit].cards.count < 13 )
   191             return nil;
   192     return self.currentPlayer;
   193 }
   194 
   195 
   196 
   197 @end