Source/Grid.m
author Jens Alfke <jens@mooseyard.com>
Sun Feb 06 16:31:03 2011 -0800 (2011-02-06)
changeset 29 0b1c315ffc64
parent 27 b0affce7beb1
permissions -rw-r--r--
Minor compiler-compatibility fixes.
     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] autorelease];
   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     size.width = roundf(size.width/2.0);
   391     size.height = roundf(size.height/2.0);
   392     CATransform3D x = CATransform3DMakeTranslation(-size.width, -size.height, 0);
   393     x = CATransform3DConcat(x, bitTransform);
   394     x = CATransform3DConcat(x, CATransform3DMakeTranslation(size.width, size.height ,0));
   395     self.sublayerTransform = x;    
   396 }
   397 
   398 
   399 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
   400 {
   401     // Default implementation just fills or outlines the cell.
   402     CGRect frame = self.frame;
   403     if( fill )
   404         CGContextFillRect(ctx,frame);
   405     else
   406         CGContextStrokeRect(ctx, frame);
   407 }
   408 
   409 
   410 - (void) setBit: (Bit*)bit
   411 {
   412     if( bit != self.bit ) {
   413         [super setBit: bit];
   414         if( bit )
   415             bit.position = GetCGRectCenter(self.bounds);
   416     }
   417 }
   418 
   419 - (Bit*) canDragBit: (Bit*)bit
   420 {
   421     if( _grid.allowsMoves && bit==self.bit )
   422         return [super canDragBit: bit];
   423     else
   424         return nil;
   425 }
   426 
   427 - (BOOL) canDropBit: (Bit*)bit atPoint: (CGPoint)point
   428 {
   429     return self.bit == nil || _grid.allowsCaptures;
   430 }
   431 
   432 
   433 - (BOOL) fwdIsN 
   434 {
   435     return self.game.currentPlayer.index == 0;
   436 }
   437 
   438 
   439 - (NSArray*) neighbors
   440 {
   441     BOOL orthogonal = ! _grid.usesDiagonals;
   442     NSMutableArray *neighbors = [NSMutableArray arrayWithCapacity: 8];
   443     for( int dy=-1; dy<=1; dy++ )
   444         for( int dx=-1; dx<=1; dx++ )
   445             if( (dx || dy) && !(orthogonal && dx && dy) ) {
   446                 GridCell *cell = [_grid cellAtRow: _row+dy column: _column+dx];
   447                 if( cell )
   448                     [neighbors addObject: cell];
   449             }
   450     return neighbors;
   451 }
   452 
   453 
   454 // Recursive subroutine used by getGroup:.
   455 - (void) x_addToGroup: (NSMutableSet*)group liberties: (NSMutableSet*)liberties owner: (Player*)owner
   456 {
   457     Bit *bit = self.bit;
   458     if( bit == nil ) {
   459         if( [liberties containsObject: self] )
   460             return; // already traversed
   461         [liberties addObject: self];
   462     } else if( bit.owner==owner ) {
   463         if( [group containsObject: self] )
   464             return; // already traversed
   465         [group addObject: self];
   466         for( GridCell *c in self.neighbors )
   467             [c x_addToGroup: group liberties: liberties owner: owner];
   468     }
   469 }
   470 
   471 
   472 - (NSSet*) getGroup: (int*)outLiberties
   473 {
   474     NSMutableSet *group=[NSMutableSet set], *liberties=nil;
   475     if( outLiberties )
   476         liberties = [NSMutableSet set];
   477     [self x_addToGroup: group liberties: liberties owner: self.bit.owner];
   478     if( outLiberties )
   479         *outLiberties = liberties.count;
   480     return group;
   481 }
   482 
   483 
   484 #pragma mark -
   485 #pragma mark DRAG-AND-DROP:
   486 
   487 
   488 #if ! TARGET_OS_IPHONE
   489 
   490 // An image from another app can be dragged onto a Grid to change its background pattern.
   491 
   492 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
   493 {
   494     if( CanGetCGImageFromPasteboard([sender draggingPasteboard]) )
   495         return NSDragOperationCopy;
   496     else
   497         return NSDragOperationNone;
   498 }
   499 
   500 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
   501 {
   502     CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard],sender);
   503     if( image ) {
   504         CGColorRef pattern = CreatePatternColor(image);
   505         _grid.cellColor = pattern;
   506         CGColorRelease(pattern);
   507         [_grid setNeedsDisplay];
   508         return YES;
   509     } else
   510         return NO;
   511 }
   512 
   513 #endif
   514 
   515 @end
   516 
   517 
   518 
   519 
   520 #pragma mark -
   521 
   522 @implementation RectGrid
   523 
   524 
   525 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
   526             spacing: (CGSize)spacing
   527            position: (CGPoint)pos
   528 {
   529     self = [super initWithRows: nRows columns: nColumns spacing: spacing position: pos];
   530     if( self ) {
   531         _cellClass = [Square class];
   532     }
   533     return self;
   534 }
   535 
   536 
   537 - (CGColorRef) altCellColor                         {return _altCellColor;}
   538 - (void) setAltCellColor: (CGColorRef)altCellColor  {setcolor(&_altCellColor,altCellColor);}
   539 
   540 
   541 @end
   542 
   543 
   544 
   545 #pragma mark -
   546 
   547 @implementation Square
   548 
   549 
   550 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
   551 {
   552     if( fill ) {
   553         CGColorRef c = ((RectGrid*)_grid).altCellColor;
   554         if( c ) {
   555             if( ! ((_row+_column) & 1) )
   556                 c = _grid.cellColor;
   557             CGContextSetFillColorWithColor(ctx, c);
   558         }
   559     }
   560     [super drawInParentContext: ctx fill: fill];
   561 }
   562 
   563 
   564 - (void) setHighlighted: (BOOL)highlighted
   565 {
   566     [super setHighlighted: highlighted];
   567     self.cornerRadius = self.bounds.size.width/2.0;
   568     self.borderWidth = (highlighted ?6 :0);
   569 }
   570 
   571 
   572 - (Square*) nw     {return (Square*)[_grid cellAtRow: _row+1 column: _column-1];}
   573 - (Square*) n      {return (Square*)[_grid cellAtRow: _row+1 column: _column  ];}
   574 - (Square*) ne     {return (Square*)[_grid cellAtRow: _row+1 column: _column+1];}
   575 - (Square*) e      {return (Square*)[_grid cellAtRow: _row   column: _column+1];}
   576 - (Square*) se     {return (Square*)[_grid cellAtRow: _row-1 column: _column+1];}
   577 - (Square*) s      {return (Square*)[_grid cellAtRow: _row-1 column: _column  ];}
   578 - (Square*) sw     {return (Square*)[_grid cellAtRow: _row-1 column: _column-1];}
   579 - (Square*) w      {return (Square*)[_grid cellAtRow: _row   column: _column-1];}
   580 
   581 // Directions relative to the current player:
   582 - (Square*) fl     {return self.fwdIsN ?self.nw :self.se;}
   583 - (Square*) f      {return self.fwdIsN ?self.n  :self.s;}
   584 - (Square*) fr     {return self.fwdIsN ?self.ne :self.sw;}
   585 - (Square*) r      {return self.fwdIsN ?self.e  :self.w;}
   586 - (Square*) br     {return self.fwdIsN ?self.se :self.nw;}
   587 - (Square*) b      {return self.fwdIsN ?self.s  :self.n;}
   588 - (Square*) bl     {return self.fwdIsN ?self.sw :self.ne;}
   589 - (Square*) l      {return self.fwdIsN ?self.w  :self.e;}
   590 
   591 
   592 static int sgn( int n ) {return n<0 ?-1 :(n>0 ?1 :0);}
   593 
   594 
   595 - (SEL) directionToCell: (GridCell*)dst
   596 {
   597     static NSString* const kDirections[9] = {@"sw", @"s", @"se",
   598                                              @"w",  nil,  @"e",
   599                                              @"nw", @"n", @"ne"};
   600     if( dst.grid != self.grid )
   601         return NULL;
   602     int dy=dst.row-_row, dx=dst.column-_column;
   603     if( dx && dy )
   604         if( !( _grid.usesDiagonals && abs(dx)==abs(dy) ) )
   605             return NULL;
   606     NSString *dir = kDirections[ 3*(sgn(dy)+1) + (sgn(dx)+1) ];
   607     return dir ?NSSelectorFromString(dir) :NULL;
   608 }
   609 
   610 - (NSArray*) lineToCell: (GridCell*)dst inclusive: (BOOL)inclusive;
   611 {
   612     SEL dir = [self directionToCell: dst];
   613     if( ! dir )
   614         return nil;
   615     NSMutableArray *line = [NSMutableArray array];
   616     GridCell *cell;
   617     for( cell=self; cell; cell = [cell performSelector: dir] ) {
   618         if( inclusive || (cell!=self && cell!=dst) )
   619             [line addObject: cell];
   620         if( cell==dst )
   621             return line;
   622     }
   623     return nil; // should be impossible, but just in case
   624 }
   625                 
   626 
   627 #if ! TARGET_OS_IPHONE
   628 
   629 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
   630 {
   631     CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard],sender);
   632     if( image ) {
   633         CGColorRef color = CreatePatternColor(image);
   634         RectGrid *rectGrid = (RectGrid*)_grid;
   635         if( rectGrid.altCellColor && ((_row+_column) & 1) )
   636             rectGrid.altCellColor = color;
   637         else
   638             rectGrid.cellColor = color;
   639         CGColorRelease(color);
   640         [rectGrid setNeedsDisplay];
   641         return YES;
   642     } else
   643         return NO;
   644 }
   645 
   646 #endif
   647 
   648 @end
   649 
   650 
   651 
   652 #pragma mark -
   653 
   654 @implementation GoSquare
   655 
   656 @synthesize dotted=_dotted;
   657 
   658 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
   659 {
   660     if( fill )
   661         [super drawInParentContext: ctx fill: fill];
   662     else {
   663         CGRect frame = self.frame;
   664         const CGFloat midx=floor(CGRectGetMidX(frame))+0.5, 
   665                     midy=floor(CGRectGetMidY(frame))+0.5;
   666         CGPoint p[4] = {{CGRectGetMinX(frame),midy},
   667                         {CGRectGetMaxX(frame),midy},
   668                         {midx,CGRectGetMinY(frame)},
   669                         {midx,CGRectGetMaxY(frame)}};
   670         if( ! self.s )  p[2].y = midy;
   671         if( ! self.n )  p[3].y = midy;
   672         if( ! self.w )  p[0].x = midx;
   673         if( ! self.e )  p[1].x = midx;
   674         CGContextStrokeLineSegments(ctx, p, 4);
   675         
   676         if( _dotted ) {
   677             CGContextSetFillColorWithColor(ctx,_grid.lineColor);
   678             CGRect dot = CGRectMake(midx-2.5, midy-2.5, 5, 5);
   679             CGContextFillEllipseInRect(ctx, dot);
   680         }
   681     }
   682 }
   683 
   684 
   685 @end