Source/Grid.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 "Grid.h"
    24 #import "Bit.h"
    25 #import "Piece.h"
    26 #import "Game+Protected.h"
    27 #import "Player.h"
    28 #import "QuartzUtils.h"
    29 
    30 
    31 @interface GridCell ()
    32 - (void) setBitTransform: (CATransform3D)bitTransform;
    33 @end
    34 
    35 
    36 @implementation Grid
    37 
    38 
    39 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
    40             spacing: (CGSize)spacing
    41            position: (CGPoint)pos
    42 {
    43     NSParameterAssert(nRows>0 && nColumns>0);
    44     self = [super init];
    45     if( self ) {
    46         _nRows = nRows;
    47         _nColumns = nColumns;
    48         _spacing = spacing;
    49         _cellClass = [GridCell class];
    50         self.lineColor = kBlackColor;
    51         _allowsMoves = YES;
    52         _usesDiagonals = YES;
    53         _bitTransform = CATransform3DIdentity;
    54 
    55         self.bounds = CGRectMake(-1, -1, nColumns*spacing.width+2, nRows*spacing.height+2);
    56         self.position = pos;
    57         self.anchorPoint = CGPointMake(0,0);
    58         self.zPosition = kBoardZ;
    59         self.needsDisplayOnBoundsChange = YES;
    60         
    61         unsigned n = nRows*nColumns;
    62         _cells = [[NSMutableArray alloc] initWithCapacity: n];
    63         id null = [NSNull null];
    64         while( n-- > 0 )
    65             [_cells addObject: null];
    66 
    67         [self setNeedsDisplay];
    68     }
    69     return self;
    70 }
    71 
    72 
    73 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
    74               frame: (CGRect)frame
    75 {
    76     CGFloat spacing = floor(MIN( (frame.size.width -2)/(CGFloat)nColumns,
    77                                (frame.size.height-2)/(CGFloat)nRows) );
    78     CGSize size = CGSizeMake(nColumns*spacing+2, nRows*spacing+2);
    79     CGPoint position = frame.origin;
    80     position.x += round( (frame.size.width -size.width )/2.0 );
    81     position.y += round( (frame.size.height-size.height)/2.0 );
    82 
    83     return [self initWithRows: nRows columns: nColumns
    84                       spacing: CGSizeMake(spacing,spacing)
    85                      position: position];
    86 }
    87 
    88 
    89 - (void) dealloc
    90 {
    91     CGColorRelease(_cellColor);
    92     CGColorRelease(_lineColor);
    93     [_cells release];
    94     [super dealloc];
    95 }
    96 
    97 
    98 static void setcolor( CGColorRef *var, CGColorRef color )
    99 {
   100     if( color != *var ) {
   101         CGColorRelease(*var);
   102         *var = CGColorRetain(color);
   103     }
   104 }
   105 
   106 - (CGColorRef) cellColor                        {return _cellColor;}
   107 - (void) setCellColor: (CGColorRef)cellColor    {setcolor(&_cellColor,cellColor);}
   108 
   109 - (CGColorRef) lineColor                        {return _lineColor;}
   110 - (void) setLineColor: (CGColorRef)lineColor    {setcolor(&_lineColor,lineColor);}
   111 
   112 - (CGImageRef) backgroundImage                  {return _backgroundImage;}
   113 - (void) setBackgroundImage: (CGImageRef)image
   114 {
   115     if( image != _backgroundImage ) {
   116         CGImageRelease(_backgroundImage);
   117         _backgroundImage = CGImageRetain(image);
   118     }
   119 }
   120 
   121 @synthesize cellClass=_cellClass, rows=_nRows, columns=_nColumns, spacing=_spacing, reversed=_reversed,
   122             usesDiagonals=_usesDiagonals, allowsMoves=_allowsMoves, allowsCaptures=_allowsCaptures,
   123             bitTransform=_bitTransform;
   124 
   125 
   126 #pragma mark -
   127 #pragma mark GEOMETRY:
   128 
   129 
   130 - (GridCell*) cellAtRow: (unsigned)row column: (unsigned)col
   131 {
   132     if( row < _nRows && col < _nColumns ) {
   133         id cell = [_cells objectAtIndex: row*_nColumns+col];
   134         if( cell != [NSNull null] )
   135             return cell;
   136     }
   137     return nil;
   138 }
   139 
   140 
   141 /** Subclasses can override this, to change the cell's class or frame. */
   142 - (GridCell*) createCellAtRow: (unsigned)row column: (unsigned)col 
   143                suggestedFrame: (CGRect)frame
   144 {
   145     GridCell *cell = [[_cellClass alloc] initWithGrid: self 
   146                                                   row: row column: col
   147                                                 frame: frame];
   148     cell.name = [NSString stringWithFormat: @"%c%u", ('A'+row),(1+col)];
   149     return [cell autorelease];
   150 }
   151 
   152 
   153 - (GridCell*) addCellAtRow: (unsigned)row column: (unsigned)col
   154 {
   155     NSParameterAssert(row<_nRows);
   156     NSParameterAssert(col<_nColumns);
   157     unsigned index = row*_nColumns+col;
   158     GridCell *cell = [_cells objectAtIndex: index];
   159     if( (id)cell == [NSNull null] ) {
   160         unsigned effectiveRow=row, effectiveCol=col;
   161         if( _reversed ) {
   162             effectiveRow = _nRows-1    - effectiveRow;
   163             effectiveCol = _nColumns-1 - effectiveCol;
   164         }
   165         CGRect frame = CGRectMake(effectiveCol*_spacing.width, effectiveRow*_spacing.height,
   166                                   _spacing.width,_spacing.height);
   167         cell = [self createCellAtRow: row column: col suggestedFrame: frame];
   168         if( cell ) {
   169             [_cells replaceObjectAtIndex: index withObject: cell];
   170             if( _reversed )
   171                 [self addSublayer: cell];
   172             else
   173                 [self insertSublayer: cell atIndex: 0];
   174             [self setNeedsDisplay];
   175         }
   176     }
   177     return cell;
   178 }
   179 
   180 
   181 - (void) addAllCells
   182 {
   183     for( int row=0; row<_nRows; row++ )
   184         for( int col=0; col<_nColumns; col++ ) 
   185             [self addCellAtRow: row column: col];
   186 }
   187 
   188 
   189 - (void) removeCellAtRow: (unsigned)row column: (unsigned)col
   190 {
   191     NSParameterAssert(row<_nRows);
   192     NSParameterAssert(col<_nColumns);
   193     unsigned index = row*_nColumns+col;
   194     id cell = [_cells objectAtIndex: index];
   195     if( cell != [NSNull null] )
   196         [cell removeFromSuperlayer];
   197     [_cells replaceObjectAtIndex: index withObject: [NSNull null]];
   198     [self setNeedsDisplay];
   199 }
   200 
   201 
   202 - (NSArray*) cells
   203 {
   204     NSMutableArray *cells = [_cells mutableCopy];
   205     for( int i=cells.count-1; i>=0; i-- )
   206         if( [cells objectAtIndex: i] == [NSNull null] )
   207             [cells removeObjectAtIndex: i];
   208     return cells;
   209 }
   210 
   211 
   212 - (GridCell*) cellWithName: (NSString*)name
   213 {
   214     for( CALayer *layer in self.sublayers )
   215         if( [layer isKindOfClass: [GridCell class]] )
   216             if( [name isEqualToString: ((GridCell*)layer).name] )
   217                 return (GridCell*)layer;
   218     return nil;
   219 }
   220 
   221 
   222 - (NSCountedSet*) countPiecesByPlayer
   223 {
   224     NSCountedSet *players = [NSCountedSet set];
   225     for( GridCell *cell in self.cells ) {
   226         Player *owner = cell.bit.owner;
   227         if( owner )
   228             [players addObject: owner];
   229     }
   230     return players;
   231 }
   232 
   233 
   234 - (CATransform3D) bitTransform
   235 {
   236     return _bitTransform;
   237 }
   238 
   239 - (void) setBitTransform: (CATransform3D)t
   240 {
   241     _bitTransform = t;
   242     for( GridCell *cell in self.cells )
   243         [cell setBitTransform: t];
   244 }
   245 
   246 - (void) updateCellTransform
   247 {
   248     CATransform3D t = self.aggregateTransform;
   249     t.m41 = t.m42 = t.m43 = 0.0f;           // remove translation component
   250     t = CATransform3DInvert(t);
   251     self.bitTransform = t;
   252 }
   253 
   254 
   255 #pragma mark -
   256 #pragma mark GAME STATE:
   257 
   258 
   259 - (NSString*) stateString
   260 {
   261     NSMutableString *state = [NSMutableString stringWithCapacity: _cells.count];
   262     for( GridCell *cell in self.cells ) {
   263         Bit *bit = cell.bit;
   264         NSString *name = bit ?bit.name :@"-";
   265         NSAssert(name.length==1,@"Missing or multicharacter name");
   266         [state appendString: name];
   267     }
   268     return state;
   269 }
   270 
   271 - (void) setStateString: (NSString*)state
   272 {
   273     Game *game = self.game;
   274     int i = 0;
   275     for( GridCell *cell in self.cells )
   276         cell.bit = [game makePieceNamed: [state substringWithRange: NSMakeRange(i++,1)]];
   277 }
   278 
   279 
   280 - (BOOL) applyMoveString: (NSString*)move
   281 {
   282     GridCell *src = nil;
   283     for( NSString *ident in [move componentsSeparatedByString: @"-"] ) {
   284         while( [ident hasSuffix: @"!"] || [ident hasSuffix: @"*"] )
   285             ident = [ident substringToIndex: ident.length-1];
   286         GridCell *dst = [self cellWithName: ident];
   287         if( dst == nil )
   288             return NO;
   289         if( src && ! [self.game animateMoveFrom: src to: dst] )
   290             return NO;
   291         src = dst;
   292     }
   293     return YES;
   294 }
   295 
   296 
   297 #pragma mark -
   298 #pragma mark DRAWING:
   299 
   300 
   301 - (void) drawCellsInContext: (CGContextRef)ctx fill: (BOOL)fill
   302 {
   303     // Subroutine of -drawInContext:. Draws all the cells, with or without a fill.
   304     for( unsigned row=0; row<_nRows; row++ )
   305         for( unsigned col=0; col<_nColumns; col++ ) {
   306             GridCell *cell = [self cellAtRow: row column: col];
   307             if( cell )
   308                 [cell drawInParentContext: ctx fill: fill];
   309         }
   310 }
   311 
   312 - (void) drawBackgroundInContext: (CGContextRef)ctx
   313 {
   314     if( _backgroundImage ) {
   315         CGRect bounds = self.bounds;
   316         if( _reversed ) {
   317             CGContextSaveGState(ctx);
   318             CGContextRotateCTM(ctx, M_PI);
   319             CGContextTranslateCTM(ctx, -bounds.size.width, -bounds.size.height);
   320         }
   321         CGContextDrawImage(ctx, bounds, _backgroundImage);
   322         if( _reversed )
   323             CGContextRestoreGState(ctx);
   324     }
   325 }
   326 
   327 
   328 - (void)drawInContext:(CGContextRef)ctx
   329 {
   330     // Custom CALayer drawing implementation. Delegates to the cells to draw themselves
   331     // in me; this is more efficient than having each cell have its own drawing.
   332     [super drawInContext: ctx];
   333     
   334     [self drawBackgroundInContext: ctx];
   335     
   336     if( _cellColor ) {
   337         CGContextSetFillColorWithColor(ctx, _cellColor);
   338         [self drawCellsInContext: ctx fill: YES];
   339     }
   340     if( _lineColor ) {
   341         CGContextSetStrokeColorWithColor(ctx,_lineColor);
   342         [self drawCellsInContext:ctx fill: NO];
   343     }
   344 }
   345 
   346 
   347 @end
   348 
   349 
   350 
   351 #pragma mark -
   352 
   353 @implementation GridCell
   354 
   355 
   356 - (id) initWithGrid: (Grid*)grid 
   357                 row: (unsigned)row column: (unsigned)col
   358               frame: (CGRect)frame
   359 {
   360     self = [super init];
   361     if (self != nil) {
   362         _grid = grid;
   363         _row = row;
   364         _column = col;
   365         self.anchorPoint = CGPointMake(0,0);
   366         self.position = frame.origin;
   367         CGRect bounds = frame;
   368         bounds.origin.x -= floor(bounds.origin.x);  // make sure my coords fall on pixel boundaries
   369         bounds.origin.y -= floor(bounds.origin.y);
   370         self.bounds = bounds;
   371         self.borderColor = kHighlightColor;         // Used when highlighting (see -setHighlighted:)
   372         [self setBitTransform: grid.bitTransform];
   373     }
   374     return self;
   375 }
   376 
   377 - (NSString*) description
   378 {
   379     return [NSString stringWithFormat: @"%@(%u,%u)", [self class],_column,_row];
   380 }
   381 
   382 @synthesize grid=_grid, row=_row, column=_column;
   383 
   384 
   385 - (void) setBitTransform: (CATransform3D)bitTransform
   386 {
   387     // To make the bitTransform relative to my center, I need to offset the center to the origin
   388     // first, and then back afterwards.
   389     CGSize size = self.bounds.size;
   390     CATransform3D x = CATransform3DMakeTranslation(-size.width/2, -size.height/2,0);
   391     x = CATransform3DConcat(x, bitTransform);
   392     x = CATransform3DConcat(x, CATransform3DMakeTranslation(size.width/2, size.height/2,0));
   393     self.sublayerTransform = x;    
   394 }
   395 
   396 
   397 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
   398 {
   399     // Default implementation just fills or outlines the cell.
   400     CGRect frame = self.frame;
   401     if( fill )
   402         CGContextFillRect(ctx,frame);
   403     else
   404         CGContextStrokeRect(ctx, frame);
   405 }
   406 
   407 
   408 - (void) setBit: (Bit*)bit
   409 {
   410     if( bit != self.bit ) {
   411         [super setBit: bit];
   412         if( bit )
   413             bit.position = GetCGRectCenter(self.bounds);
   414     }
   415 }
   416 
   417 - (Bit*) canDragBit: (Bit*)bit
   418 {
   419     if( _grid.allowsMoves && bit==self.bit )
   420         return [super canDragBit: bit];
   421     else
   422         return nil;
   423 }
   424 
   425 - (BOOL) canDropBit: (Bit*)bit atPoint: (CGPoint)point
   426 {
   427     return self.bit == nil || _grid.allowsCaptures;
   428 }
   429 
   430 
   431 - (BOOL) fwdIsN 
   432 {
   433     return self.game.currentPlayer.index == 0;
   434 }
   435 
   436 
   437 - (NSArray*) neighbors
   438 {
   439     BOOL orthogonal = ! _grid.usesDiagonals;
   440     NSMutableArray *neighbors = [NSMutableArray arrayWithCapacity: 8];
   441     for( int dy=-1; dy<=1; dy++ )
   442         for( int dx=-1; dx<=1; dx++ )
   443             if( (dx || dy) && !(orthogonal && dx && dy) ) {
   444                 GridCell *cell = [_grid cellAtRow: _row+dy column: _column+dx];
   445                 if( cell )
   446                     [neighbors addObject: cell];
   447             }
   448     return neighbors;
   449 }
   450 
   451 
   452 // Recursive subroutine used by getGroup:.
   453 - (void) x_addToGroup: (NSMutableSet*)group liberties: (NSMutableSet*)liberties owner: (Player*)owner
   454 {
   455     Bit *bit = self.bit;
   456     if( bit == nil ) {
   457         if( [liberties containsObject: self] )
   458             return; // already traversed
   459         [liberties addObject: self];
   460     } else if( bit.owner==owner ) {
   461         if( [group containsObject: self] )
   462             return; // already traversed
   463         [group addObject: self];
   464         for( GridCell *c in self.neighbors )
   465             [c x_addToGroup: group liberties: liberties owner: owner];
   466     }
   467 }
   468 
   469 
   470 - (NSSet*) getGroup: (int*)outLiberties
   471 {
   472     NSMutableSet *group=[NSMutableSet set], *liberties=nil;
   473     if( outLiberties )
   474         liberties = [NSMutableSet set];
   475     [self x_addToGroup: group liberties: liberties owner: self.bit.owner];
   476     if( outLiberties )
   477         *outLiberties = liberties.count;
   478     return group;
   479 }
   480 
   481 
   482 #pragma mark -
   483 #pragma mark DRAG-AND-DROP:
   484 
   485 
   486 #if ! TARGET_OS_IPHONE
   487 
   488 // An image from another app can be dragged onto a Grid to change its background pattern.
   489 
   490 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
   491 {
   492     if( CanGetCGImageFromPasteboard([sender draggingPasteboard]) )
   493         return NSDragOperationCopy;
   494     else
   495         return NSDragOperationNone;
   496 }
   497 
   498 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
   499 {
   500     CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard],sender);
   501     if( image ) {
   502         CGColorRef pattern = CreatePatternColor(image);
   503         _grid.cellColor = pattern;
   504         CGColorRelease(pattern);
   505         [_grid setNeedsDisplay];
   506         return YES;
   507     } else
   508         return NO;
   509 }
   510 
   511 #endif
   512 
   513 @end
   514 
   515 
   516 
   517 
   518 #pragma mark -
   519 
   520 @implementation RectGrid
   521 
   522 
   523 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
   524             spacing: (CGSize)spacing
   525            position: (CGPoint)pos
   526 {
   527     self = [super initWithRows: nRows columns: nColumns spacing: spacing position: pos];
   528     if( self ) {
   529         _cellClass = [Square class];
   530     }
   531     return self;
   532 }
   533 
   534 
   535 - (CGColorRef) altCellColor                         {return _altCellColor;}
   536 - (void) setAltCellColor: (CGColorRef)altCellColor  {setcolor(&_altCellColor,altCellColor);}
   537 
   538 
   539 @end
   540 
   541 
   542 
   543 #pragma mark -
   544 
   545 @implementation Square
   546 
   547 
   548 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
   549 {
   550     if( fill ) {
   551         CGColorRef c = ((RectGrid*)_grid).altCellColor;
   552         if( c ) {
   553             if( ! ((_row+_column) & 1) )
   554                 c = _grid.cellColor;
   555             CGContextSetFillColorWithColor(ctx, c);
   556         }
   557     }
   558     [super drawInParentContext: ctx fill: fill];
   559 }
   560 
   561 
   562 - (void) setHighlighted: (BOOL)highlighted
   563 {
   564     [super setHighlighted: highlighted];
   565     self.cornerRadius = self.bounds.size.width/2.0;
   566     self.borderWidth = (highlighted ?6 :0);
   567 }
   568 
   569 
   570 - (Square*) nw     {return (Square*)[_grid cellAtRow: _row+1 column: _column-1];}
   571 - (Square*) n      {return (Square*)[_grid cellAtRow: _row+1 column: _column  ];}
   572 - (Square*) ne     {return (Square*)[_grid cellAtRow: _row+1 column: _column+1];}
   573 - (Square*) e      {return (Square*)[_grid cellAtRow: _row   column: _column+1];}
   574 - (Square*) se     {return (Square*)[_grid cellAtRow: _row-1 column: _column+1];}
   575 - (Square*) s      {return (Square*)[_grid cellAtRow: _row-1 column: _column  ];}
   576 - (Square*) sw     {return (Square*)[_grid cellAtRow: _row-1 column: _column-1];}
   577 - (Square*) w      {return (Square*)[_grid cellAtRow: _row   column: _column-1];}
   578 
   579 // Directions relative to the current player:
   580 - (Square*) fl     {return self.fwdIsN ?self.nw :self.se;}
   581 - (Square*) f      {return self.fwdIsN ?self.n  :self.s;}
   582 - (Square*) fr     {return self.fwdIsN ?self.ne :self.sw;}
   583 - (Square*) r      {return self.fwdIsN ?self.e  :self.w;}
   584 - (Square*) br     {return self.fwdIsN ?self.se :self.nw;}
   585 - (Square*) b      {return self.fwdIsN ?self.s  :self.n;}
   586 - (Square*) bl     {return self.fwdIsN ?self.sw :self.ne;}
   587 - (Square*) l      {return self.fwdIsN ?self.w  :self.e;}
   588 
   589 
   590 static int sgn( int n ) {return n<0 ?-1 :(n>0 ?1 :0);}
   591 
   592 
   593 - (SEL) directionToCell: (GridCell*)dst
   594 {
   595     static NSString* const kDirections[9] = {@"sw", @"s", @"se",
   596                                              @"w",  nil,  @"e",
   597                                              @"nw", @"n", @"ne"};
   598     if( dst.grid != self.grid )
   599         return NULL;
   600     int dy=dst.row-_row, dx=dst.column-_column;
   601     if( dx && dy )
   602         if( !( _grid.usesDiagonals && abs(dx)==abs(dy) ) )
   603             return NULL;
   604     NSString *dir = kDirections[ 3*(sgn(dy)+1) + (sgn(dx)+1) ];
   605     return dir ?NSSelectorFromString(dir) :NULL;
   606 }
   607 
   608 - (NSArray*) lineToCell: (GridCell*)dst inclusive: (BOOL)inclusive;
   609 {
   610     SEL dir = [self directionToCell: dst];
   611     if( ! dir )
   612         return nil;
   613     NSMutableArray *line = [NSMutableArray array];
   614     GridCell *cell;
   615     for( cell=self; cell; cell = [cell performSelector: dir] ) {
   616         if( inclusive || (cell!=self && cell!=dst) )
   617             [line addObject: cell];
   618         if( cell==dst )
   619             return line;
   620     }
   621     return nil; // should be impossible, but just in case
   622 }
   623                 
   624 
   625 #if ! TARGET_OS_IPHONE
   626 
   627 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
   628 {
   629     CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard],sender);
   630     if( image ) {
   631         CGColorRef color = CreatePatternColor(image);
   632         RectGrid *rectGrid = (RectGrid*)_grid;
   633         if( rectGrid.altCellColor && ((_row+_column) & 1) )
   634             rectGrid.altCellColor = color;
   635         else
   636             rectGrid.cellColor = color;
   637         CGColorRelease(color);
   638         [rectGrid setNeedsDisplay];
   639         return YES;
   640     } else
   641         return NO;
   642 }
   643 
   644 #endif
   645 
   646 @end
   647 
   648 
   649 
   650 #pragma mark -
   651 
   652 @implementation GoSquare
   653 
   654 @synthesize dotted=_dotted;
   655 
   656 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
   657 {
   658     if( fill )
   659         [super drawInParentContext: ctx fill: fill];
   660     else {
   661         CGRect frame = self.frame;
   662         const CGFloat midx=floor(CGRectGetMidX(frame))+0.5, 
   663                     midy=floor(CGRectGetMidY(frame))+0.5;
   664         CGPoint p[4] = {{CGRectGetMinX(frame),midy},
   665                         {CGRectGetMaxX(frame),midy},
   666                         {midx,CGRectGetMinY(frame)},
   667                         {midx,CGRectGetMaxY(frame)}};
   668         if( ! self.s )  p[2].y = midy;
   669         if( ! self.n )  p[3].y = midy;
   670         if( ! self.w )  p[0].x = midx;
   671         if( ! self.e )  p[1].x = midx;
   672         CGContextStrokeLineSegments(ctx, p, 4);
   673         
   674         if( _dotted ) {
   675             CGContextSetFillColorWithColor(ctx,_grid.lineColor);
   676             CGRect dot = CGRectMake(midx-2.5, midy-2.5, 5, 5);
   677             CGContextFillEllipseInRect(ctx, dot);
   678         }
   679     }
   680 }
   681 
   682 
   683 @end