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