Source/KlondikeGame.m
author Jens Alfke <jens@mooseyard.com>
Fri Mar 07 11:43:02 2008 -0800 (2008-03-07)
changeset 0 e9f7ba4718e1
child 1 3eb7be1dd7b6
permissions -rw-r--r--
Initial check-in into Mercurial. Branched from 1.0 release of Apple's sample code. No longer requires garbage collection. Fixed some memory leaks of CG objects. Fixed a bug when advancing to the 8th row in the Checkers 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@0
    27
jens@0
    28
jens@0
    29
#define kStackHeight 500
jens@0
    30
jens@0
    31
jens@0
    32
/**  WARNING: THIS CODE REQUIRES GARBAGE COLLECTION!
jens@0
    33
 **  This sample application uses Objective-C 2.0 garbage collection.
jens@0
    34
 **  Therefore, the source code in this file does NOT perform manual object memory management.
jens@0
    35
 **  If you reuse any of this code in a process that isn't garbage collected, you will need to
jens@0
    36
 **  add all necessary retain/release/autorelease calls, and implement -dealloc methods,
jens@0
    37
 **  otherwise unpleasant leakage will occur!
jens@0
    38
 **/
jens@0
    39
jens@0
    40
jens@0
    41
@implementation KlondikeGame
jens@0
    42
jens@0
    43
jens@0
    44
- (id) initWithBoard: (CALayer*)board
jens@0
    45
{
jens@0
    46
    self = [super initWithBoard: board];
jens@0
    47
    if (self != nil) {
jens@0
    48
        [self setNumberOfPlayers: 1];
jens@0
    49
        
jens@0
    50
        _deck = [[Deck alloc] initWithCardsOfClass: [PlayingCard class]];
jens@0
    51
        [_deck shuffle];
jens@0
    52
        _deck.position = CGPointMake(kCardWidth/2+16,kCardHeight/2+16);
jens@0
    53
        [board addSublayer: _deck];
jens@0
    54
        
jens@0
    55
        _sink = [[Deck alloc] init];
jens@0
    56
        _sink.position = CGPointMake(3*kCardWidth/2+32,kCardHeight/2+16);
jens@0
    57
        [board addSublayer: _sink];
jens@0
    58
        
jens@0
    59
        for( CardSuit suit=kSuitClubs; suit<=kSuitSpades; suit++ ) {
jens@0
    60
            Deck *aces = [[Deck alloc] init];
jens@0
    61
            aces.position = CGPointMake(kCardWidth/2+16+(kCardWidth+16)*(suit%2),
jens@0
    62
                                        120+kCardHeight+(kCardHeight+16)*(suit/2));
jens@0
    63
            [board addSublayer: aces];
jens@0
    64
            _aces[suit] = aces;
jens@0
    65
        }
jens@0
    66
        
jens@0
    67
        for( int s=0; s<7; s++ ) {
jens@0
    68
            Stack *stack = [[Stack alloc] initWithStartPos: CGPointMake(kCardWidth/2,
jens@0
    69
                                                                        kStackHeight-kCardHeight/2.0)
jens@0
    70
                                                   spacing: CGSizeMake(0,-22)];
jens@0
    71
            stack.frame = CGRectMake(260+s*(kCardWidth+16),16, kCardWidth,kStackHeight);
jens@0
    72
            stack.backgroundColor = nil;
jens@0
    73
            stack.dragAsStacks = YES;
jens@0
    74
            [board addSublayer: stack];
jens@0
    75
            
jens@0
    76
            // According to the rules, one card should be added to each stack in turn, instead
jens@0
    77
            // of populating entire stacks one at a time. However, if one trusts the Deck's
jens@0
    78
            // -shuffle method (which uses the random() function, seeded with a high-entropy
jens@0
    79
            // cryptographically-strong value), it shouldn't make any difference :-)
jens@0
    80
            for( int c=0; c<=s; c++ )
jens@0
    81
                [stack addBit: [_deck removeTopCard]];
jens@0
    82
            ((Card*)stack.bits.lastObject).faceUp = YES;
jens@0
    83
        }
jens@0
    84
        
jens@0
    85
        [self nextPlayer];
jens@0
    86
    }
jens@0
    87
    return self;
jens@0
    88
}
jens@0
    89
jens@0
    90
jens@0
    91
- (BOOL) clickedBit: (Bit*)bit
jens@0
    92
{
jens@0
    93
    if( [bit isKindOfClass: [Card class]] ) {
jens@0
    94
        Card *card = (Card*)bit;
jens@0
    95
        if( card.holder == _deck ) {
jens@0
    96
            // Click on deck deals 3 cards to the sink:
jens@0
    97
            for( int i=0; i<3; i++ ) {
jens@0
    98
                Card *card = [_deck removeTopCard];
jens@0
    99
                if( card ) {
jens@0
   100
                    [_sink addCard: card];
jens@0
   101
                    card.faceUp = YES;
jens@0
   102
                }
jens@0
   103
            }
jens@0
   104
            [self endTurn];
jens@0
   105
            return YES;
jens@0
   106
        } else if( card.holder == _sink ) {
jens@0
   107
            // Clicking the sink when the deck is empty re-deals:
jens@0
   108
            if( _deck.empty ) {
jens@0
   109
                [_deck addCards: [_sink removeAllCards]];
jens@0
   110
                [_deck flip];
jens@0
   111
                [self endTurn];
jens@0
   112
                return YES;
jens@0
   113
            }
jens@0
   114
        } else {
jens@0
   115
            // Click on a card elsewhere turns it face-up:
jens@0
   116
            if( ! card.faceUp ) {
jens@0
   117
                card.faceUp = YES;
jens@0
   118
                return YES;
jens@0
   119
            }
jens@0
   120
        }
jens@0
   121
    }
jens@0
   122
    return NO;
jens@0
   123
}
jens@0
   124
jens@0
   125
jens@0
   126
- (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src
jens@0
   127
{
jens@0
   128
    if( [bit isKindOfClass: [DraggedStack class]] ) {
jens@0
   129
        Card *bottomSrc = [[(DraggedStack*)bit bits] objectAtIndex: 0];
jens@0
   130
        if( ! bottomSrc.faceUp )
jens@0
   131
            return NO;
jens@0
   132
    }
jens@0
   133
    return YES;
jens@0
   134
}
jens@0
   135
jens@0
   136
jens@0
   137
- (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
jens@0
   138
{
jens@0
   139
    if( src==_deck || dst==_deck || dst==_sink )
jens@0
   140
        return NO;
jens@0
   141
    
jens@0
   142
    // Find the bottom card being moved, and the top card it's moving onto:
jens@0
   143
    PlayingCard *bottomSrc;
jens@0
   144
    if( [bit isKindOfClass: [DraggedStack class]] )
jens@0
   145
        bottomSrc = [[(DraggedStack*)bit bits] objectAtIndex: 0];
jens@0
   146
    else
jens@0
   147
        bottomSrc = (PlayingCard*)bit;
jens@0
   148
    
jens@0
   149
    PlayingCard *topDst;
jens@0
   150
    if( [dst isKindOfClass: [Deck class]] ) {
jens@0
   151
        // Dragging to an ace pile:
jens@0
   152
        if( ! [bit isKindOfClass: [Card class]] )
jens@0
   153
            return NO;
jens@0
   154
        topDst = (PlayingCard*) ((Deck*)dst).topCard;
jens@0
   155
        if( topDst == nil )
jens@0
   156
            return bottomSrc.rank == kRankAce;
jens@0
   157
        else
jens@0
   158
            return bottomSrc.suit == topDst.suit && bottomSrc.rank == topDst.rank+1;
jens@0
   159
        
jens@0
   160
    } else {
jens@0
   161
        // Dragging to a card stack:
jens@0
   162
        topDst = (PlayingCard*) ((Stack*)dst).topBit;
jens@0
   163
        if( topDst == nil )
jens@0
   164
            return bottomSrc.rank == kRankKing;
jens@0
   165
        else
jens@0
   166
            return bottomSrc.color != topDst.color && bottomSrc.rank == topDst.rank-1;
jens@0
   167
    }
jens@0
   168
}
jens@0
   169
jens@0
   170
jens@0
   171
- (Player*) checkForWinner
jens@0
   172
{
jens@0
   173
    for( CardSuit suit=kSuitClubs; suit<=kSuitSpades; suit++ )
jens@0
   174
        if( _aces[suit].cards.count < 13 )
jens@0
   175
            return nil;
jens@0
   176
    return _currentPlayer;
jens@0
   177
}
jens@0
   178
jens@0
   179
jens@0
   180
jens@0
   181
@end