Source/CheckersGame.m
author Jens Alfke <jens@mooseyard.com>
Thu Jul 31 20:01:26 2008 -0700 (2008-07-31)
changeset 24 db8640a38faf
parent 22 4cb50131788f
child 25 b53fa38013fc
permissions -rw-r--r--
* Put the spots in the right place in 13x13 and 19x19 boards.
* Fixed some front/back layering issues in Grid.
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 "CheckersGame.h"
jens@0
    24
#import "Grid.h"
jens@0
    25
#import "Piece.h"
jens@0
    26
#import "QuartzUtils.h"
jens@1
    27
#import "GGBUtils.h"
jens@0
    28
jens@0
    29
jens@22
    30
#define kKingScale 1.4
jens@22
    31
jens@22
    32
jens@0
    33
@implementation CheckersGame
jens@0
    34
jens@0
    35
jens@11
    36
static NSMutableDictionary *kPieceStyle1, *kPieceStyle2;
jens@11
    37
jens@11
    38
+ (void) initialize
jens@11
    39
{
jens@11
    40
    if( self == [CheckersGame class] ) {
jens@11
    41
        kPieceStyle1 = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
jens@11
    42
                        (id)GetCGImageNamed(@"Green.png"), @"contents",
jens@11
    43
                        kCAGravityResizeAspect, @"contentsGravity",
jens@11
    44
                        kCAFilterLinear, @"minificationFilter",
jens@11
    45
                        nil];
jens@11
    46
        kPieceStyle2 = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
jens@11
    47
                        (id)GetCGImageNamed(@"Red.png"), @"contents",
jens@11
    48
                        kCAGravityResizeAspect, @"contentsGravity",
jens@11
    49
                        kCAFilterLinear, @"minificationFilter",
jens@11
    50
                        nil];
jens@11
    51
    }
jens@11
    52
}
jens@11
    53
jens@11
    54
jens@10
    55
- (id) init
jens@10
    56
{
jens@10
    57
    self = [super init];
jens@10
    58
    if (self != nil) {
jens@10
    59
        [self setNumberOfPlayers: 2];
jens@10
    60
    }
jens@10
    61
    return self;
jens@10
    62
}
jens@10
    63
jens@10
    64
- (CGImageRef) iconForPlayer: (int)playerNum
jens@10
    65
{
jens@10
    66
    return GetCGImageNamed( playerNum==0 ?@"Green.png" :@"Red.png" );
jens@10
    67
}
jens@10
    68
jens@22
    69
- (void) _transformPiece: (Piece*)piece
jens@22
    70
{
jens@22
    71
    CGFloat scale = piece.tag ?kKingScale :1.0;
jens@22
    72
    piece.transform = CATransform3DMakeScale(scale, scale/cos(self.tablePerspectiveAngle), scale);
jens@22
    73
    piece.anchorPoint = CGPointMake(0.5, 0.5*cos(self.tablePerspectiveAngle));
jens@22
    74
}
jens@22
    75
jens@7
    76
- (Piece*) pieceForPlayer: (int)playerNum
jens@0
    77
{
jens@11
    78
    Piece *p = [[Piece alloc] init];
jens@16
    79
    p.bounds = CGRectMake(0,0,floor(_board.spacing.width),floor(_board.spacing.height));
jens@11
    80
    p.style = (playerNum ?kPieceStyle2 :kPieceStyle1);
jens@7
    81
    p.owner = [self.players objectAtIndex: playerNum];
jens@7
    82
    p.name = playerNum ?@"2" :@"1";
jens@22
    83
    [self _transformPiece: p];
jens@7
    84
    return [p autorelease];
jens@0
    85
}
jens@0
    86
jens@22
    87
- (void) perspectiveChanged
jens@22
    88
{
jens@22
    89
    for( GridCell *cell in _board.cells ) {
jens@22
    90
        Piece *piece = (Piece*) cell.bit;
jens@22
    91
        if( piece )
jens@22
    92
            [self _transformPiece: piece];
jens@22
    93
    }
jens@22
    94
}
jens@22
    95
jens@10
    96
- (void) makeKing: (Piece*)piece
jens@10
    97
{
jens@12
    98
    piece.tag = YES;        // tag property stores the 'king' flag
jens@10
    99
    piece.name = piece.owner.index ?@"4" :@"3";
jens@23
   100
    [self _transformPiece: piece];
jens@10
   101
}
jens@10
   102
jens@10
   103
- (void) setUpBoard
jens@0
   104
{
jens@19
   105
    PreloadSound(@"Tink");
jens@19
   106
    PreloadSound(@"Funk");
jens@19
   107
    PreloadSound(@"Blow");
jens@19
   108
    PreloadSound(@"Pop");
jens@19
   109
jens@16
   110
    RectGrid *board = [[RectGrid alloc] initWithRows: 8 columns: 8 frame: _table.bounds];
jens@16
   111
    _board = board;
jens@16
   112
    [_table addSublayer: _board];
jens@16
   113
    CGPoint pos = _board.position;
jens@16
   114
    pos.x = floor((_table.bounds.size.width-board.frame.size.width)/2);
jens@16
   115
    board.position = pos;
jens@16
   116
    board.allowsMoves = YES;
jens@16
   117
    board.allowsCaptures = NO;
jens@16
   118
    board.cellColor    = CreateGray(0.0, 0.5);
jens@16
   119
    board.altCellColor = CreateGray(1.0, 0.25);
jens@16
   120
    board.lineColor = nil;
jens@16
   121
    board.reversed = ! [[self.players objectAtIndex: 0] isLocal];
jens@7
   122
jens@7
   123
    for( int i=0; i<32; i++ ) {
jens@7
   124
        int row = i/4;
jens@16
   125
        [_board addCellAtRow: row column: 2*(i%4) + (row&1)];
jens@7
   126
    }
jens@16
   127
    [_board release]; // its superlayer still retains it
jens@0
   128
}
jens@0
   129
jens@12
   130
- (NSString*) initialStateString            {return @"111111111111--------222222222222";}
jens@16
   131
- (NSString*) stateString                   {return _board.stateString;}
jens@16
   132
- (void) setStateString: (NSString*)state   {_board.stateString = state;}
jens@12
   133
jens@12
   134
- (Piece*) makePieceNamed: (NSString*)name
jens@7
   135
{
jens@12
   136
    int which = [name characterAtIndex: 0] - '1';
jens@12
   137
    if( which >=0 && which < 4 ) {
jens@12
   138
        Piece *piece = [self pieceForPlayer: (which & 1)];
jens@12
   139
        if( which & 2 ) 
jens@12
   140
            [self makeKing: piece];
jens@12
   141
        return piece;
jens@12
   142
    } else
jens@12
   143
        return nil;
jens@7
   144
}
jens@7
   145
jens@0
   146
jens@0
   147
- (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)srcHolder to: (id<BitHolder>)dstHolder
jens@0
   148
{
jens@0
   149
    Square *src=(Square*)srcHolder, *dst=(Square*)dstHolder;
jens@12
   150
    if( bit.tag )
jens@0
   151
        if( dst==src.bl || dst==src.br || dst==src.l || dst==src.r
jens@0
   152
           || (src.bl.bit.unfriendly && dst==src.bl.bl) || (src.br.bit.unfriendly && dst==src.br.br) )
jens@0
   153
            return YES;    
jens@0
   154
    return dst==src.fl || dst==src.fr
jens@0
   155
        || (src.fl.bit.unfriendly && dst==src.fl.fl) || (src.fr.bit.unfriendly && dst==src.fr.fr);
jens@0
   156
}
jens@0
   157
jens@0
   158
- (void) bit: (Bit*)bit movedFrom: (id<BitHolder>)srcHolder to: (id<BitHolder>)dstHolder
jens@0
   159
{
jens@0
   160
    Square *src=(Square*)srcHolder, *dst=(Square*)dstHolder;
jens@0
   161
    int playerIndex = self.currentPlayer.index;
jens@7
   162
    
jens@10
   163
    Turn *turn = self.currentTurn;
jens@10
   164
    if( turn.move.length==0 )
jens@10
   165
        [turn addToMove: src.name];
jens@10
   166
    [turn addToMove: @"-"];
jens@10
   167
    [turn addToMove: dst.name];
jens@7
   168
    
jens@12
   169
    BOOL isKing = bit.tag;
jens@1
   170
    PlaySound(isKing ?@"Funk" :@"Tink");
jens@0
   171
jens@0
   172
    // "King" a piece that made it to the last row:
jens@12
   173
    if( !isKing && (dst.row == (playerIndex ?0 :7)) ) {
jens@12
   174
        PlaySound(@"Blow");
jens@12
   175
        [self makeKing: (Piece*)bit];
jens@12
   176
        [turn addToMove: @"*"];
jens@12
   177
        // don't set isKing flag - piece can't jump again after being kinged.
jens@12
   178
    }
jens@0
   179
jens@0
   180
    // Check for a capture:
jens@12
   181
    NSArray *line = [src lineToCell: dst inclusive: NO];
jens@12
   182
    if( line.count==1 ) {
jens@12
   183
        Square *capture = [line objectAtIndex: 0];
jens@10
   184
        [capture destroyBit];
jens@10
   185
        [turn addToMove: @"!"];
jens@12
   186
        PlaySound(@"Pop");
jens@0
   187
        
jens@0
   188
        // Now check if another capture is possible. If so, don't end the turn:
jens@0
   189
        if( (dst.fl.bit.unfriendly && dst.fl.fl.empty) || (dst.fr.bit.unfriendly && dst.fr.fr.empty) )
jens@0
   190
            return;
jens@0
   191
        if( isKing )
jens@0
   192
            if( (dst.bl.bit.unfriendly && dst.bl.bl.empty) || (dst.br.bit.unfriendly && dst.br.br.empty) )
jens@0
   193
                return;
jens@0
   194
    }
jens@0
   195
    
jens@0
   196
    [self endTurn];
jens@0
   197
}
jens@0
   198
jens@20
   199
#pragma mark -
jens@20
   200
#pragma mark CHECK FOR WIN:
jens@20
   201
jens@20
   202
static BOOL canOpponentMoveOrJump( GridCell *first, GridCell *second ) {
jens@20
   203
    return first.empty || (first.bit.friendly && second.empty);
jens@20
   204
}
jens@20
   205
jens@20
   206
- (BOOL) canOpponentMoveFrom: (GridCell*)src
jens@20
   207
{
jens@20
   208
    if( ! src.bit.unfriendly )
jens@20
   209
        return NO;
jens@20
   210
    Square *square = (Square*)src;
jens@20
   211
    if( square.bit.tag )           // remember, it's opponent's piece, so directions are reversed
jens@20
   212
        if( canOpponentMoveOrJump(square.fl,square.fl.fl) || canOpponentMoveOrJump(square.fr,square.fr.fr) )
jens@20
   213
            return YES;
jens@20
   214
    return canOpponentMoveOrJump(square.bl,square.bl.bl) || canOpponentMoveOrJump(square.br,square.br.br);
jens@20
   215
}
jens@20
   216
jens@0
   217
- (Player*) checkForWinner
jens@0
   218
{
jens@20
   219
    for( GridCell *cell in _board.cells )
jens@20
   220
        if( [self canOpponentMoveFrom: cell] ) {
jens@20
   221
            Log(@"Checkers: %@ can move from %@",self.currentPlayer.nextPlayer,cell);
jens@20
   222
            return nil;
jens@20
   223
        }
jens@20
   224
    return self.currentPlayer;
jens@0
   225
}
jens@0
   226
jens@0
   227
jens@7
   228
- (BOOL) applyMoveString: (NSString*)move
jens@7
   229
{
jens@7
   230
    GridCell *src = nil;
jens@10
   231
    for( NSString *ident in [move componentsSeparatedByString: @"-"] ) {
jens@10
   232
        while( [ident hasSuffix: @"!"] || [ident hasSuffix: @"*"] )
jens@10
   233
            ident = [ident substringToIndex: ident.length-1];
jens@16
   234
        GridCell *dst = [_board cellWithName: ident];
jens@10
   235
        if( dst == nil )
jens@10
   236
            return NO;
jens@10
   237
        if( src && ! [self animateMoveFrom: src to: dst] )
jens@10
   238
            return NO;
jens@7
   239
        src = dst;
jens@7
   240
    }
jens@7
   241
    return YES;
jens@7
   242
}
jens@7
   243
jens@7
   244
jens@0
   245
@end