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