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