Source/CheckersGame.m
author Jens Alfke <jens@mooseyard.com>
Mon Jul 21 17:32:21 2008 -0700 (2008-07-21)
changeset 21 2eb229411d73
parent 19 3b750982ff39
child 22 4cb50131788f
permissions -rw-r--r--
* Added API to Stack for removing bits.
* GoGame correctly saves/restores number of captured pieces.
* Improved positioning of captured-piece Stacks in GoGame.
* New Go piece icons.
* Added "Warn" function to GGBUtils.
     1 /*  This code is based on Apple's "GeekGameBoard" sample code, version 1.0.
     2     http://developer.apple.com/samplecode/GeekGameBoard/
     3     Copyright © 2007 Apple Inc. Copyright © 2008 Jens Alfke. All Rights Reserved.
     4 
     5     Redistribution and use in source and binary forms, with or without modification, are permitted
     6     provided that the following conditions are met:
     7 
     8     * Redistributions of source code must retain the above copyright notice, this list of conditions
     9       and the following disclaimer.
    10     * Redistributions in binary form must reproduce the above copyright notice, this list of
    11       conditions and the following disclaimer in the documentation and/or other materials provided
    12       with the distribution.
    13 
    14     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
    15     IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
    16     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
    17     BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    18     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
    19     PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
    20     CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
    21     THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    22 */
    23 #import "CheckersGame.h"
    24 #import "Grid.h"
    25 #import "Piece.h"
    26 #import "QuartzUtils.h"
    27 #import "GGBUtils.h"
    28 
    29 
    30 @implementation CheckersGame
    31 
    32 
    33 static NSMutableDictionary *kPieceStyle1, *kPieceStyle2;
    34 
    35 + (void) initialize
    36 {
    37     if( self == [CheckersGame class] ) {
    38         kPieceStyle1 = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
    39                         (id)GetCGImageNamed(@"Green.png"), @"contents",
    40                         kCAGravityResizeAspect, @"contentsGravity",
    41                         kCAFilterLinear, @"minificationFilter",
    42                         nil];
    43         kPieceStyle2 = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
    44                         (id)GetCGImageNamed(@"Red.png"), @"contents",
    45                         kCAGravityResizeAspect, @"contentsGravity",
    46                         kCAFilterLinear, @"minificationFilter",
    47                         nil];
    48     }
    49 }
    50 
    51 
    52 - (id) init
    53 {
    54     self = [super init];
    55     if (self != nil) {
    56         [self setNumberOfPlayers: 2];
    57     }
    58     return self;
    59 }
    60 
    61 - (CGImageRef) iconForPlayer: (int)playerNum
    62 {
    63     return GetCGImageNamed( playerNum==0 ?@"Green.png" :@"Red.png" );
    64 }
    65 
    66 - (Piece*) pieceForPlayer: (int)playerNum
    67 {
    68     Piece *p = [[Piece alloc] init];
    69     p.bounds = CGRectMake(0,0,floor(_board.spacing.width),floor(_board.spacing.height));
    70     p.style = (playerNum ?kPieceStyle2 :kPieceStyle1);
    71     p.owner = [self.players objectAtIndex: playerNum];
    72     p.name = playerNum ?@"2" :@"1";
    73     return [p autorelease];
    74 }
    75 
    76 - (void) makeKing: (Piece*)piece
    77 {
    78     piece.scale = 1.4;
    79     piece.tag = YES;        // tag property stores the 'king' flag
    80     piece.name = piece.owner.index ?@"4" :@"3";
    81 }
    82 
    83 - (void) setUpBoard
    84 {
    85     PreloadSound(@"Tink");
    86     PreloadSound(@"Funk");
    87     PreloadSound(@"Blow");
    88     PreloadSound(@"Pop");
    89 
    90     RectGrid *board = [[RectGrid alloc] initWithRows: 8 columns: 8 frame: _table.bounds];
    91     _board = board;
    92     [_table addSublayer: _board];
    93     CGPoint pos = _board.position;
    94     pos.x = floor((_table.bounds.size.width-board.frame.size.width)/2);
    95     board.position = pos;
    96     board.allowsMoves = YES;
    97     board.allowsCaptures = NO;
    98     board.cellColor    = CreateGray(0.0, 0.5);
    99     board.altCellColor = CreateGray(1.0, 0.25);
   100     board.lineColor = nil;
   101     board.reversed = ! [[self.players objectAtIndex: 0] isLocal];
   102 
   103     for( int i=0; i<32; i++ ) {
   104         int row = i/4;
   105         [_board addCellAtRow: row column: 2*(i%4) + (row&1)];
   106     }
   107     [_board release]; // its superlayer still retains it
   108 }
   109 
   110 - (NSString*) initialStateString            {return @"111111111111--------222222222222";}
   111 - (NSString*) stateString                   {return _board.stateString;}
   112 - (void) setStateString: (NSString*)state   {_board.stateString = state;}
   113 
   114 - (Piece*) makePieceNamed: (NSString*)name
   115 {
   116     int which = [name characterAtIndex: 0] - '1';
   117     if( which >=0 && which < 4 ) {
   118         Piece *piece = [self pieceForPlayer: (which & 1)];
   119         if( which & 2 ) 
   120             [self makeKing: piece];
   121         return piece;
   122     } else
   123         return nil;
   124 }
   125 
   126 
   127 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)srcHolder to: (id<BitHolder>)dstHolder
   128 {
   129     Square *src=(Square*)srcHolder, *dst=(Square*)dstHolder;
   130     if( bit.tag )
   131         if( dst==src.bl || dst==src.br || dst==src.l || dst==src.r
   132            || (src.bl.bit.unfriendly && dst==src.bl.bl) || (src.br.bit.unfriendly && dst==src.br.br) )
   133             return YES;    
   134     return dst==src.fl || dst==src.fr
   135         || (src.fl.bit.unfriendly && dst==src.fl.fl) || (src.fr.bit.unfriendly && dst==src.fr.fr);
   136 }
   137 
   138 - (void) bit: (Bit*)bit movedFrom: (id<BitHolder>)srcHolder to: (id<BitHolder>)dstHolder
   139 {
   140     Square *src=(Square*)srcHolder, *dst=(Square*)dstHolder;
   141     int playerIndex = self.currentPlayer.index;
   142     
   143     Turn *turn = self.currentTurn;
   144     if( turn.move.length==0 )
   145         [turn addToMove: src.name];
   146     [turn addToMove: @"-"];
   147     [turn addToMove: dst.name];
   148     
   149     BOOL isKing = bit.tag;
   150     PlaySound(isKing ?@"Funk" :@"Tink");
   151 
   152     // "King" a piece that made it to the last row:
   153     if( !isKing && (dst.row == (playerIndex ?0 :7)) ) {
   154         PlaySound(@"Blow");
   155         [self makeKing: (Piece*)bit];
   156         [turn addToMove: @"*"];
   157         // don't set isKing flag - piece can't jump again after being kinged.
   158     }
   159 
   160     // Check for a capture:
   161     NSArray *line = [src lineToCell: dst inclusive: NO];
   162     if( line.count==1 ) {
   163         Square *capture = [line objectAtIndex: 0];
   164         [capture destroyBit];
   165         [turn addToMove: @"!"];
   166         PlaySound(@"Pop");
   167         
   168         // Now check if another capture is possible. If so, don't end the turn:
   169         if( (dst.fl.bit.unfriendly && dst.fl.fl.empty) || (dst.fr.bit.unfriendly && dst.fr.fr.empty) )
   170             return;
   171         if( isKing )
   172             if( (dst.bl.bit.unfriendly && dst.bl.bl.empty) || (dst.br.bit.unfriendly && dst.br.br.empty) )
   173                 return;
   174     }
   175     
   176     [self endTurn];
   177 }
   178 
   179 #pragma mark -
   180 #pragma mark CHECK FOR WIN:
   181 
   182 static BOOL canOpponentMoveOrJump( GridCell *first, GridCell *second ) {
   183     return first.empty || (first.bit.friendly && second.empty);
   184 }
   185 
   186 - (BOOL) canOpponentMoveFrom: (GridCell*)src
   187 {
   188     if( ! src.bit.unfriendly )
   189         return NO;
   190     Square *square = (Square*)src;
   191     if( square.bit.tag )           // remember, it's opponent's piece, so directions are reversed
   192         if( canOpponentMoveOrJump(square.fl,square.fl.fl) || canOpponentMoveOrJump(square.fr,square.fr.fr) )
   193             return YES;
   194     return canOpponentMoveOrJump(square.bl,square.bl.bl) || canOpponentMoveOrJump(square.br,square.br.br);
   195 }
   196 
   197 - (Player*) checkForWinner
   198 {
   199     for( GridCell *cell in _board.cells )
   200         if( [self canOpponentMoveFrom: cell] ) {
   201             Log(@"Checkers: %@ can move from %@",self.currentPlayer.nextPlayer,cell);
   202             return nil;
   203         }
   204     return self.currentPlayer;
   205 }
   206 
   207 
   208 - (BOOL) applyMoveString: (NSString*)move
   209 {
   210     GridCell *src = nil;
   211     for( NSString *ident in [move componentsSeparatedByString: @"-"] ) {
   212         while( [ident hasSuffix: @"!"] || [ident hasSuffix: @"*"] )
   213             ident = [ident substringToIndex: ident.length-1];
   214         GridCell *dst = [_board cellWithName: ident];
   215         if( dst == nil )
   216             return NO;
   217         if( src && ! [self animateMoveFrom: src to: dst] )
   218             return NO;
   219         src = dst;
   220     }
   221     return YES;
   222 }
   223 
   224 
   225 @end