Source/GoGame.m
author Jens Alfke <jens@mooseyard.com>
Thu Jul 31 11:18:13 2008 -0700 (2008-07-31)
changeset 22 4cb50131788f
parent 21 2eb229411d73
child 24 db8640a38faf
permissions -rw-r--r--
* Working on 3D rotation of game board.
* Added some colored balls ("drawn" myself using Photoshop glass effect.)
     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 "GoGame.h"
    24 #import "Grid.h"
    25 #import "Piece.h"
    26 #import "Dispenser.h"
    27 #import "Stack.h"
    28 #import "QuartzUtils.h"
    29 #import "GGBUtils.h"
    30 
    31 
    32 @implementation GoGame
    33 
    34 
    35 + (int) dimensions {return 19;}
    36 
    37 - (id) init
    38 {
    39     self = [super init];
    40     if (self != nil) {
    41         [self setNumberOfPlayers: 2];
    42         [(Player*)[_players objectAtIndex: 0] setName: @"Black"];
    43         [(Player*)[_players objectAtIndex: 1] setName: @"White"];
    44     }
    45     return self;
    46 }
    47         
    48 - (void) setUpBoard
    49 {
    50     int dimensions = [[self class] dimensions];
    51     CGRect tableBounds = _table.bounds;
    52     CGSize size = tableBounds.size;
    53     CGFloat boardSide = MIN(size.width* dimensions/(CGFloat)(dimensions+2),size.height);
    54     RectGrid *board = [[RectGrid alloc] initWithRows: dimensions columns: dimensions 
    55                                               frame: CGRectMake(floor((size.width-boardSide)/2),
    56                                                                 floor((size.height-boardSide)/2),
    57                                                                 boardSide,boardSide)];
    58     _board = board;
    59     /*
    60     grid.backgroundColor = GetCGPatternNamed(@"Wood.jpg");
    61     grid.borderColor = kTranslucentLightGrayColor;
    62     grid.borderWidth = 2;
    63     */
    64     board.lineColor = kTranslucentGrayColor;
    65     board.cellClass = [GoSquare class];
    66     [board addAllCells];
    67     ((GoSquare*)[board cellAtRow: 2 column: 2]).dotted = YES;
    68     ((GoSquare*)[board cellAtRow: 6 column: 6]).dotted = YES;
    69     ((GoSquare*)[board cellAtRow: 2 column: 6]).dotted = YES;
    70     ((GoSquare*)[board cellAtRow: 6 column: 2]).dotted = YES;
    71     board.usesDiagonals = board.allowsMoves = board.allowsCaptures = NO;
    72     [_table addSublayer: board];
    73     [board release];
    74     
    75     CGRect gridFrame = board.frame;
    76     CGFloat pieceSize = (int)board.spacing.width & ~1;  // make sure it's even
    77     CGFloat captureMinY = CGRectGetMinY(tableBounds) + pieceSize/2,
    78             captureHeight = size.height - pieceSize;
    79     _captured[0] = [[Stack alloc] initWithStartPos: CGPointMake(pieceSize/2,0)
    80                                            spacing: CGSizeMake(0,pieceSize)
    81                                       wrapInterval: floor(captureHeight/pieceSize)
    82                                        wrapSpacing: CGSizeMake(pieceSize,0)];
    83     _captured[0].frame = CGRectMake(CGRectGetMinX(tableBounds), 
    84                                     captureMinY,
    85                                     CGRectGetMinX(gridFrame)-CGRectGetMinX(tableBounds),
    86                                     captureHeight);
    87     _captured[0].zPosition = kPieceZ+1;
    88     [_table addSublayer: _captured[0]];
    89     [_captured[0] release];
    90     
    91     _captured[1] = [[Stack alloc] initWithStartPos: CGPointMake(pieceSize/2,captureHeight)
    92                                            spacing: CGSizeMake(0,-pieceSize)
    93                                       wrapInterval: floor(captureHeight/pieceSize)
    94                                        wrapSpacing: CGSizeMake(-pieceSize,0)];
    95     _captured[1].frame = CGRectMake(CGRectGetMaxX(gridFrame), 
    96                                     captureMinY,
    97                                     CGRectGetMaxX(tableBounds)-CGRectGetMaxX(gridFrame),
    98                                     captureHeight);
    99     _captured[1].startPos = CGPointMake(CGRectGetMaxX(_captured[1].bounds)-pieceSize/2, captureHeight);
   100     _captured[1].zPosition = kPieceZ+1;
   101     [_table addSublayer: _captured[1]];
   102     [_captured[1] release];
   103 
   104     PreloadSound(@"Pop");
   105 }
   106 
   107 - (CGImageRef) iconForPlayer: (int)playerNum
   108 {
   109     return GetCGImageNamed( playerNum ?@"ball-white.png" :@"ball-black.png" );
   110 }
   111 
   112 - (Piece*) pieceForPlayer: (int)index
   113 {
   114     NSString *imageName = index ?@"ball-white.png" :@"ball-black.png";
   115     CGFloat pieceSize = (int)(_board.spacing.width * 1.0) & ~1;  // make sure it's even
   116     Piece *stone = [[Piece alloc] initWithImageNamed: imageName scale: pieceSize];
   117     stone.owner = [self.players objectAtIndex: index];
   118     return [stone autorelease];
   119 }
   120 
   121 - (Bit*) bitToPlaceInHolder: (id<BitHolder>)holder
   122 {
   123     if( holder.bit != nil || ! [holder isKindOfClass: [GoSquare class]] )
   124         return nil;
   125     else
   126         return [self pieceForPlayer: self.currentPlayer.index];
   127 }
   128 
   129 
   130 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)srcHolder
   131 {
   132     return (srcHolder==nil);
   133 }
   134 
   135 
   136 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)srcHolder to: (id<BitHolder>)dstHolder
   137 {
   138     if( srcHolder!=nil || ! [dstHolder isKindOfClass: [Square class]] )
   139         return NO;
   140     Square *dst=(Square*)dstHolder;
   141     
   142     // There should be a check here for a "ko" (repeated position) ... exercise for the reader!
   143     
   144     // Check for suicidal move. First an easy check for an empty adjacent space:
   145     NSArray *neighbors = dst.neighbors;
   146     for( GridCell *c in neighbors )
   147         if( c.empty )
   148             return YES;                     // there's an empty space
   149     // If the piece is surrounded, check the neighboring groups' liberties:
   150     for( GridCell *c in neighbors ) {
   151         int nLiberties;
   152         [c getGroup: &nLiberties];
   153         if( c.bit.unfriendly ) {
   154             if( nLiberties <= 1 )
   155                 return YES;             // the move captures, so it's not suicidal
   156         } else {
   157             if( nLiberties > 1 )
   158                 return YES;             // the stone joins a group with other liberties
   159         }
   160     }
   161     return NO;
   162 }
   163 
   164 
   165 - (void) bit: (Bit*)bit movedFrom: (id<BitHolder>)srcHolder to: (id<BitHolder>)dstHolder
   166 {
   167     Square *dst=(Square*)dstHolder;
   168     int curIndex = self.currentPlayer.index;
   169     // Check for captured enemy groups:
   170     BOOL captured = NO;
   171     for( GridCell *c in dst.neighbors )
   172         if( c.bit.unfriendly ) {
   173             int nLiberties;
   174             NSSet *group = [c getGroup: &nLiberties];
   175             if( nLiberties == 0 ) {
   176                 captured = YES;
   177                 for( GridCell *capture in group )
   178                     [_captured[curIndex] addBit: capture.bit];  // Moves piece to POW camp!
   179             }
   180         }
   181     if( captured )
   182         PlaySound(@"Pop");
   183     
   184     [self.currentTurn addToMove: dst.name];
   185     [self endTurn];
   186 }
   187 
   188 
   189 // This sample code makes no attempt to detect the end of the game, or count score,
   190 // both of which are rather complex to decide in Go.
   191 
   192 
   193 #pragma mark -
   194 #pragma mark STATE:
   195 
   196 
   197 - (NSString*) stateString
   198 {
   199     int n = _board.rows;
   200     unichar state[n*n];
   201     for( int y=0; y<n; y++ )
   202         for( int x=0; x<n; x++ ) {
   203             Bit *bit = [_board cellAtRow: y column: x].bit;
   204             unichar ch;
   205             if( bit==nil )
   206                 ch = '-';
   207             else
   208                 ch = '1' + bit.owner.index;
   209             state[y*n+x] = ch;
   210         }
   211     NSMutableString *stateString = [NSMutableString stringWithCharacters: state length: n*n];
   212     
   213     NSUInteger cap0=_captured[0].numberOfBits, cap1=_captured[1].numberOfBits;
   214     if( cap0 || cap1 )
   215         [stateString appendFormat: @",%i,%i", cap0,cap1];
   216     return stateString;
   217 }
   218 
   219 - (void) setStateString: (NSString*)state
   220 {
   221     //NSLog(@"Go: setStateString: '%@'",state);
   222     NSArray *components = [state componentsSeparatedByString: @","];
   223     state = [components objectAtIndex: 0];
   224     int n = _board.rows;
   225     for( int y=0; y<n; y++ )
   226         for( int x=0; x<n; x++ ) {
   227             int i = y*n+x;
   228             Piece *piece = nil;
   229             if( i < state.length ) {
   230                 int index = [state characterAtIndex: i] - '1';
   231                 if( index==0 || index==1 )
   232                     piece = [self pieceForPlayer: index];
   233             }
   234             [_board cellAtRow: y column: x].bit = piece;
   235         }
   236     
   237     if( components.count < 3 )
   238         components = nil;
   239     for( int player=0; player<=1; player++ ) {
   240         NSUInteger nCaptured = [[components objectAtIndex: 1+player] intValue];
   241         NSUInteger curNCaptured = _captured[player].numberOfBits;
   242         if( nCaptured < curNCaptured )
   243            _captured[player].numberOfBits = nCaptured;
   244         else
   245             for( int i=curNCaptured; i<nCaptured; i++ )
   246                 [_captured[player] addBit: [self pieceForPlayer: 1-player]];
   247     }
   248 }
   249 
   250 
   251 - (BOOL) applyMoveString: (NSString*)move
   252 {
   253     //NSLog(@"Go: applyMoveString: '%@'",move);
   254     return [self animatePlacementIn: [_board cellWithName: move]];
   255 }
   256 
   257 
   258 @end
   259 
   260 
   261 @implementation Go9Game
   262 + (NSString*) displayName   {return @"Go (9x9)";}
   263 + (int) dimensions          {return 9;}
   264 @end
   265 
   266 
   267 @implementation Go13Game
   268 + (NSString*) displayName   {return @"Go (13x13)";}
   269 + (int) dimensions          {return 13;}
   270 @end