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