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.
jens@0
     1
/*  This code is based on Apple's "GeekGameBoard" sample code, version 1.0.
jens@0
     2
    http://developer.apple.com/samplecode/GeekGameBoard/
jens@0
     3
    Copyright © 2007 Apple Inc. Copyright © 2008 Jens Alfke. All Rights Reserved.
jens@0
     4
jens@0
     5
    Redistribution and use in source and binary forms, with or without modification, are permitted
jens@0
     6
    provided that the following conditions are met:
jens@0
     7
jens@0
     8
    * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@0
     9
      and the following disclaimer.
jens@0
    10
    * Redistributions in binary form must reproduce the above copyright notice, this list of
jens@0
    11
      conditions and the following disclaimer in the documentation and/or other materials provided
jens@0
    12
      with the distribution.
jens@0
    13
jens@0
    14
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@0
    15
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
jens@0
    16
    FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@0
    17
    BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@0
    18
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
jens@0
    19
    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
jens@0
    20
    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
jens@0
    21
    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@0
    22
*/
jens@0
    23
#import "KlondikeGame.h"
jens@0
    24
#import "Deck.h"
jens@0
    25
#import "PlayingCard.h"
jens@0
    26
#import "Stack.h"
jens@4
    27
#import "QuartzUtils.h"
jens@0
    28
jens@0
    29
jens@0
    30
#define kStackHeight 500
jens@0
    31
jens@0
    32
jens@4
    33
@implementation KlondikeGame
jens@0
    34
jens@0
    35
jens@4
    36
+ (BOOL) landscapeOriented
jens@4
    37
{
jens@4
    38
    return YES;
jens@4
    39
}
jens@0
    40
jens@0
    41
jens@10
    42
- (id) init
jens@0
    43
{
jens@10
    44
    self = [super init];
jens@10
    45
    if (self != nil)
jens@0
    46
        [self setNumberOfPlayers: 1];
jens@10
    47
    return self;
jens@10
    48
}
jens@10
    49
jens@0
    50
        
jens@10
    51
- (void) setUpBoard
jens@10
    52
{
jens@10
    53
    CGSize boardSize = _board.bounds.size;
jens@10
    54
    CGFloat xSpacing = floor(boardSize.width/7);
jens@10
    55
    CGSize kCardSize;
jens@10
    56
    kCardSize.width  = round(xSpacing * 0.9);  // 1/7th of width, with 10% gap
jens@10
    57
    kCardSize.height = round(kCardSize.width * 1.5);
jens@10
    58
    CGFloat gap = xSpacing-kCardSize.width;
jens@10
    59
    [Card setCardSize: kCardSize];
jens@10
    60
    
jens@10
    61
    CGPoint pos = {floor(gap/2)+kCardSize.width/2, floor(boardSize.height-kCardSize.height/2)};
jens@10
    62
    [_deck release];
jens@10
    63
    _deck = [[Deck alloc] initWithCardsOfClass: [PlayingCard class]];
jens@10
    64
    [_deck shuffle];
jens@10
    65
    _deck.position = pos;
jens@10
    66
    [_board addSublayer: _deck];
jens@10
    67
    
jens@10
    68
    pos.x += xSpacing;
jens@10
    69
    [_sink release];
jens@10
    70
    _sink = [[Deck alloc] init];
jens@10
    71
    _sink.position = pos;
jens@10
    72
    [_board addSublayer: _sink];
jens@10
    73
    
jens@10
    74
    pos.x += xSpacing;
jens@10
    75
    for( CardSuit suit=kSuitClubs; suit<=kSuitSpades; suit++ ) {
jens@10
    76
        pos.x += xSpacing;
jens@10
    77
        Deck *aces = [[Deck alloc] init];
jens@10
    78
        aces.position = pos;
jens@10
    79
        [_board addSublayer: aces];
jens@10
    80
        [_aces[suit] release];
jens@10
    81
        _aces[suit] = aces;
jens@10
    82
    }
jens@10
    83
    
jens@10
    84
    CGRect stackFrame = {{floor(gap/2), gap}, 
jens@10
    85
                         {kCardSize.width, boardSize.height-kCardSize.height-2*gap}};
jens@10
    86
    CGPoint startPos = CGPointMake(kCardSize.width/2,kCardSize.height/2);
jens@10
    87
    CGSize spacing = {0, floor((stackFrame.size.height-kCardSize.height)/11.0)};
jens@10
    88
    for( int s=0; s<7; s++ ) {
jens@10
    89
        Stack *stack = [[Stack alloc] initWithStartPos: startPos spacing: spacing];
jens@10
    90
        stack.frame = stackFrame;
jens@10
    91
        stackFrame.origin.x += xSpacing;
jens@10
    92
        stack.backgroundColor = nil; //kAlmostInvisibleWhiteColor;
jens@10
    93
        stack.dragAsStacks = YES;
jens@10
    94
        [_board addSublayer: stack];
jens@4
    95
        
jens@10
    96
        // According to the rules, one card should be added to each stack in turn, instead
jens@10
    97
        // of populating entire stacks one at a time. However, if one trusts the Deck's
jens@10
    98
        // -shuffle method (which uses the random() function, seeded with a high-entropy
jens@10
    99
        // cryptographically-strong value), it shouldn't make any difference :-)
jens@10
   100
        for( int c=0; c<=s; c++ )
jens@10
   101
            [stack addBit: [_deck removeTopCard]];
jens@10
   102
        ((Card*)stack.bits.lastObject).faceUp = YES;
jens@0
   103
    }
jens@0
   104
}
jens@0
   105
jens@0
   106
jens@0
   107
- (BOOL) clickedBit: (Bit*)bit
jens@0
   108
{
jens@0
   109
    if( [bit isKindOfClass: [Card class]] ) {
jens@0
   110
        Card *card = (Card*)bit;
jens@0
   111
        if( card.holder == _deck ) {
jens@0
   112
            // Click on deck deals 3 cards to the sink:
jens@0
   113
            for( int i=0; i<3; i++ ) {
jens@0
   114
                Card *card = [_deck removeTopCard];
jens@0
   115
                if( card ) {
jens@0
   116
                    [_sink addCard: card];
jens@0
   117
                    card.faceUp = YES;
jens@0
   118
                }
jens@0
   119
            }
jens@0
   120
            [self endTurn];
jens@0
   121
            return YES;
jens@0
   122
        } else if( card.holder == _sink ) {
jens@0
   123
            // Clicking the sink when the deck is empty re-deals:
jens@0
   124
            if( _deck.empty ) {
jens@0
   125
                [_deck addCards: [_sink removeAllCards]];
jens@0
   126
                [_deck flip];
jens@0
   127
                [self endTurn];
jens@0
   128
                return YES;
jens@0
   129
            }
jens@0
   130
        } else {
jens@0
   131
            // Click on a card elsewhere turns it face-up:
jens@0
   132
            if( ! card.faceUp ) {
jens@0
   133
                card.faceUp = YES;
jens@0
   134
                return YES;
jens@0
   135
            }
jens@0
   136
        }
jens@0
   137
    }
jens@0
   138
    return NO;
jens@0
   139
}
jens@0
   140
jens@0
   141
jens@0
   142
- (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src
jens@0
   143
{
jens@0
   144
    if( [bit isKindOfClass: [DraggedStack class]] ) {
jens@0
   145
        Card *bottomSrc = [[(DraggedStack*)bit bits] objectAtIndex: 0];
jens@0
   146
        if( ! bottomSrc.faceUp )
jens@0
   147
            return NO;
jens@0
   148
    }
jens@0
   149
    return YES;
jens@0
   150
}
jens@0
   151
jens@0
   152
jens@0
   153
- (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
jens@0
   154
{
jens@0
   155
    if( src==_deck || dst==_deck || dst==_sink )
jens@0
   156
        return NO;
jens@0
   157
    
jens@0
   158
    // Find the bottom card being moved, and the top card it's moving onto:
jens@0
   159
    PlayingCard *bottomSrc;
jens@0
   160
    if( [bit isKindOfClass: [DraggedStack class]] )
jens@0
   161
        bottomSrc = [[(DraggedStack*)bit bits] objectAtIndex: 0];
jens@0
   162
    else
jens@0
   163
        bottomSrc = (PlayingCard*)bit;
jens@0
   164
    
jens@0
   165
    PlayingCard *topDst;
jens@0
   166
    if( [dst isKindOfClass: [Deck class]] ) {
jens@0
   167
        // Dragging to an ace pile:
jens@0
   168
        if( ! [bit isKindOfClass: [Card class]] )
jens@0
   169
            return NO;
jens@0
   170
        topDst = (PlayingCard*) ((Deck*)dst).topCard;
jens@0
   171
        if( topDst == nil )
jens@0
   172
            return bottomSrc.rank == kRankAce;
jens@0
   173
        else
jens@0
   174
            return bottomSrc.suit == topDst.suit && bottomSrc.rank == topDst.rank+1;
jens@0
   175
        
jens@0
   176
    } else {
jens@0
   177
        // Dragging to a card stack:
jens@0
   178
        topDst = (PlayingCard*) ((Stack*)dst).topBit;
jens@0
   179
        if( topDst == nil )
jens@0
   180
            return bottomSrc.rank == kRankKing;
jens@0
   181
        else
jens@0
   182
            return bottomSrc.color != topDst.color && bottomSrc.rank == topDst.rank-1;
jens@0
   183
    }
jens@0
   184
}
jens@0
   185
jens@0
   186
jens@0
   187
- (Player*) checkForWinner
jens@0
   188
{
jens@0
   189
    for( CardSuit suit=kSuitClubs; suit<=kSuitSpades; suit++ )
jens@0
   190
        if( _aces[suit].cards.count < 13 )
jens@0
   191
            return nil;
jens@10
   192
    return self.currentPlayer;
jens@0
   193
}
jens@0
   194
jens@0
   195
jens@0
   196
jens@0
   197
@end