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