Source/KlondikeGame.m
author Jens Alfke <jens@mooseyard.com>
Sun Mar 16 15:06:47 2008 -0700 (2008-03-16)
changeset 7 428a194e3e59
parent 1 3eb7be1dd7b6
child 10 6c78cc6bd7a6
permissions -rw-r--r--
Game class now tracks board state and moves, as strings, and can step through its history.
Fixed another bug in Go (you could drag your captured stones back to the board!)
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