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