Source/GoGame.m
author Jens Alfke <jens@mooseyard.com>
Thu Jul 03 17:44:30 2008 -0700 (2008-07-03)
changeset 10 6c78cc6bd7a6
parent 7 428a194e3e59
child 16 28392c9a969f
permissions -rw-r--r--
Lots of reworking. Completed support for game history, including Turn class. Changed Game API around quite a bit.
     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: @"Red"];
    43         [(Player*)[_players objectAtIndex: 1] setName: @"White"];
    44     }
    45     return self;
    46 }
    47         
    48 - (void) setUpBoard
    49 {
    50     int dimensions = [[self class] dimensions];
    51     CGSize size = _board.bounds.size;
    52     CGFloat boardSide = MIN(size.width,size.height);
    53     RectGrid *grid = [[RectGrid alloc] initWithRows: dimensions columns: dimensions 
    54                                               frame: CGRectMake(floor((size.width-boardSide)/2),
    55                                                                 floor((size.height-boardSide)/2),
    56                                                                 boardSide,boardSide)];
    57     _grid = grid;
    58     /*
    59     grid.backgroundColor = GetCGPatternNamed(@"Wood.jpg");
    60     grid.borderColor = kTranslucentLightGrayColor;
    61     grid.borderWidth = 2;
    62     */
    63     grid.lineColor = kTranslucentGrayColor;
    64     grid.cellClass = [GoSquare class];
    65     [grid addAllCells];
    66     ((GoSquare*)[grid cellAtRow: 2 column: 2]).dotted = YES;
    67     ((GoSquare*)[grid cellAtRow: 6 column: 6]).dotted = YES;
    68     ((GoSquare*)[grid cellAtRow: 2 column: 6]).dotted = YES;
    69     ((GoSquare*)[grid cellAtRow: 6 column: 2]).dotted = YES;
    70     grid.usesDiagonals = grid.allowsMoves = grid.allowsCaptures = NO;
    71     [_board addSublayer: grid];
    72     [grid release];
    73     
    74     CGRect gridFrame = grid.frame;
    75     CGFloat pieceSize = (int)grid.spacing.width & ~1;  // make sure it's even
    76     CGFloat captureHeight = gridFrame.size.height-4*pieceSize;
    77     _captured[0] = [[Stack alloc] initWithStartPos: CGPointMake(2*pieceSize,0)
    78                                            spacing: CGSizeMake(0,pieceSize)
    79                                       wrapInterval: floor(captureHeight/pieceSize)
    80                                        wrapSpacing: CGSizeMake(-pieceSize,0)];
    81     _captured[0].frame = CGRectMake(CGRectGetMinX(gridFrame)-3*pieceSize, 
    82                                       CGRectGetMinY(gridFrame)+3*pieceSize,
    83                                       2*pieceSize, captureHeight);
    84     _captured[0].zPosition = kPieceZ+1;
    85     [_board addSublayer: _captured[0]];
    86     [_captured[0] release];
    87     
    88     _captured[1] = [[Stack alloc] initWithStartPos: CGPointMake(0,captureHeight)
    89                                            spacing: CGSizeMake(0,-pieceSize)
    90                                       wrapInterval: floor(captureHeight/pieceSize)
    91                                        wrapSpacing: CGSizeMake(pieceSize,0)];
    92     _captured[1].frame = CGRectMake(CGRectGetMaxX(gridFrame)+pieceSize, 
    93                                       CGRectGetMinY(gridFrame)+pieceSize,
    94                                       2*pieceSize, captureHeight);
    95     _captured[1].zPosition = kPieceZ+1;
    96     [_board addSublayer: _captured[1]];
    97     [_captured[1] release];
    98 
    99     PreloadSound(@"Pop");
   100 }
   101 
   102 - (CGImageRef) iconForPlayer: (int)playerNum
   103 {
   104     return GetCGImageNamed( playerNum ?@"bot086.png" :@"bot089.png" );
   105 }
   106 
   107 - (Piece*) pieceForPlayer: (int)index
   108 {
   109     NSString *imageName = index ?@"bot086.png" :@"bot089.png";
   110     CGFloat pieceSize = (int)(_grid.spacing.width * 0.9) & ~1;  // make sure it's even
   111     Piece *stone = [[Piece alloc] initWithImageNamed: imageName scale: pieceSize];
   112     stone.owner = [self.players objectAtIndex: index];
   113     return [stone autorelease];
   114 }
   115 
   116 - (Bit*) bitToPlaceInHolder: (id<BitHolder>)holder
   117 {
   118     if( holder.bit != nil || ! [holder isKindOfClass: [GoSquare class]] )
   119         return nil;
   120     else
   121         return [self pieceForPlayer: self.currentPlayer.index];
   122 }
   123 
   124 
   125 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)srcHolder
   126 {
   127     return (srcHolder==nil);
   128 }
   129 
   130 
   131 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)srcHolder to: (id<BitHolder>)dstHolder
   132 {
   133     if( srcHolder!=nil || ! [dstHolder isKindOfClass: [Square class]] )
   134         return NO;
   135     Square *dst=(Square*)dstHolder;
   136     
   137     // There should be a check here for a "ko" (repeated position) ... exercise for the reader!
   138     
   139     // Check for suicidal move. First an easy check for an empty adjacent space:
   140     NSArray *neighbors = dst.neighbors;
   141     for( GridCell *c in neighbors )
   142         if( c.empty )
   143             return YES;                     // there's an empty space
   144     // If the piece is surrounded, check the neighboring groups' liberties:
   145     for( GridCell *c in neighbors ) {
   146         int nLiberties;
   147         [c getGroup: &nLiberties];
   148         if( c.bit.unfriendly ) {
   149             if( nLiberties <= 1 )
   150                 return YES;             // the move captures, so it's not suicidal
   151         } else {
   152             if( nLiberties > 1 )
   153                 return YES;             // the stone joins a group with other liberties
   154         }
   155     }
   156     return NO;
   157 }
   158 
   159 
   160 - (void) bit: (Bit*)bit movedFrom: (id<BitHolder>)srcHolder to: (id<BitHolder>)dstHolder
   161 {
   162     Square *dst=(Square*)dstHolder;
   163     int curIndex = self.currentPlayer.index;
   164     // Check for captured enemy groups:
   165     BOOL captured = NO;
   166     for( GridCell *c in dst.neighbors )
   167         if( c.bit.unfriendly ) {
   168             int nLiberties;
   169             NSSet *group = [c getGroup: &nLiberties];
   170             if( nLiberties == 0 ) {
   171                 captured = YES;
   172                 for( GridCell *capture in group )
   173                     [_captured[curIndex] addBit: capture.bit];  // Moves piece to POW camp!
   174             }
   175         }
   176     if( captured )
   177         PlaySound(@"Pop");
   178     
   179     [self.currentTurn addToMove: dst.name];
   180     [self endTurn];
   181 }
   182 
   183 
   184 // This sample code makes no attempt to detect the end of the game, or count score,
   185 // both of which are rather complex to decide in Go.
   186 
   187 
   188 #pragma mark -
   189 #pragma mark STATE:
   190 
   191 
   192 - (NSString*) stateString
   193 {
   194     int n = _grid.rows;
   195     unichar state[n*n];
   196     for( int y=0; y<n; y++ )
   197         for( int x=0; x<n; x++ ) {
   198             Bit *bit = [_grid cellAtRow: y column: x].bit;
   199             unichar ch;
   200             if( bit==nil )
   201                 ch = '-';
   202             else
   203                 ch = '1' + bit.owner.index;
   204             state[y*n+x] = ch;
   205         }
   206     return [NSString stringWithCharacters: state length: n*n];
   207 }
   208 
   209 - (void) setStateString: (NSString*)state
   210 {
   211     NSLog(@"Go: setStateString: '%@'",state);
   212     int n = _grid.rows;
   213     for( int y=0; y<n; y++ )
   214         for( int x=0; x<n; x++ ) {
   215             int i = y*n+x;
   216             Piece *piece = nil;
   217             if( i < state.length ) {
   218                 int index = [state characterAtIndex: i] - '1';
   219                 if( index==0 || index==1 )
   220                     piece = [self pieceForPlayer: index];
   221             }
   222             [_grid cellAtRow: y column: x].bit = piece;
   223         }
   224 }
   225 
   226 
   227 - (BOOL) applyMoveString: (NSString*)move
   228 {
   229     NSLog(@"Go: applyMoveString: '%@'",move);
   230     return [self animatePlacementIn: [_grid cellWithName: move]];
   231 }
   232 
   233 
   234 @end
   235 
   236 
   237 @implementation Go9Game
   238 + (NSString*) displayName   {return @"Go (9x9)";}
   239 + (int) dimensions          {return 9;}
   240 @end
   241 
   242 
   243 @implementation Go13Game
   244 + (NSString*) displayName   {return @"Go (13x13)";}
   245 + (int) dimensions          {return 13;}
   246 @end