Source/Grid.m
author Jens Alfke <jens@mooseyard.com>
Fri Mar 07 11:43:02 2008 -0800 (2008-03-07)
changeset 0 e9f7ba4718e1
child 1 3eb7be1dd7b6
permissions -rw-r--r--
Initial check-in into Mercurial. Branched from 1.0 release of Apple's sample code. No longer requires garbage collection. Fixed some memory leaks of CG objects. Fixed a bug when advancing to the 8th row in the Checkers game.
     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 "Game.h"
    26 #import "QuartzUtils.h"
    27 
    28 
    29 @implementation Grid
    30 
    31 
    32 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
    33             spacing: (CGSize)spacing
    34            position: (CGPoint)pos
    35 {
    36     NSParameterAssert(nRows>0 && nColumns>0);
    37     self = [super init];
    38     if( self ) {
    39         _nRows = nRows;
    40         _nColumns = nColumns;
    41         _spacing = spacing;
    42         _cellClass = [GridCell class];
    43         _lineColor = kBlackColor;
    44         _allowsMoves = YES;
    45         _usesDiagonals = YES;
    46 
    47         self.bounds = CGRectMake(-1, -1, nColumns*spacing.width+2, nRows*spacing.height+2);
    48         self.position = pos;
    49         self.anchorPoint = CGPointMake(0,0);
    50         self.zPosition = kBoardZ;
    51         self.needsDisplayOnBoundsChange = YES;
    52         
    53         unsigned n = nRows*nColumns;
    54         _cells = [[NSMutableArray alloc] initWithCapacity: n];
    55         id null = [NSNull null];
    56         while( n-- > 0 )
    57             [_cells addObject: null];
    58 
    59         [self setNeedsDisplay];
    60     }
    61     return self;
    62 }
    63 
    64 
    65 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
    66               frame: (CGRect)frame
    67 {
    68     CGFloat spacing = floor(MIN( (frame.size.width -2)/(CGFloat)nColumns,
    69                                (frame.size.height-2)/(CGFloat)nRows) );
    70     return [self initWithRows: nRows columns: nColumns
    71                       spacing: CGSizeMake(spacing,spacing)
    72                      position: frame.origin];
    73 }
    74 
    75 
    76 - (void) dealloc
    77 {
    78     CGColorRelease(_cellColor);
    79     CGColorRelease(_lineColor);
    80     [_cells release];
    81     [super dealloc];
    82 }
    83 
    84 
    85 static void setcolor( CGColorRef *var, CGColorRef color )
    86 {
    87     if( color != *var ) {
    88         CGColorRelease(*var);
    89         *var = CGColorRetain(color);
    90     }
    91 }
    92 
    93 - (CGColorRef) cellColor                        {return _cellColor;}
    94 - (void) setCellColor: (CGColorRef)cellColor    {setcolor(&_cellColor,cellColor);}
    95 
    96 - (CGColorRef) lineColor                        {return _lineColor;}
    97 - (void) setLineColor: (CGColorRef)lineColor    {setcolor(&_lineColor,lineColor);}
    98 
    99 @synthesize cellClass=_cellClass, rows=_nRows, columns=_nColumns, spacing=_spacing,
   100             usesDiagonals=_usesDiagonals, allowsMoves=_allowsMoves, allowsCaptures=_allowsCaptures;
   101 
   102 
   103 #pragma mark -
   104 #pragma mark GEOMETRY:
   105 
   106 
   107 - (GridCell*) cellAtRow: (unsigned)row column: (unsigned)col
   108 {
   109     if( row < _nRows && col < _nColumns ) {
   110         id cell = [_cells objectAtIndex: row*_nColumns+col];
   111         if( cell != [NSNull null] )
   112             return cell;
   113     }
   114     return nil;
   115 }
   116 
   117 
   118 /** Subclasses can override this, to change the cell's class or frame. */
   119 - (GridCell*) createCellAtRow: (unsigned)row column: (unsigned)col 
   120                suggestedFrame: (CGRect)frame
   121 {
   122     return [[[_cellClass alloc] initWithGrid: self 
   123                                         row: row column: col
   124                                       frame: frame]
   125                     autorelease];
   126 }
   127 
   128 
   129 - (GridCell*) addCellAtRow: (unsigned)row column: (unsigned)col
   130 {
   131     NSParameterAssert(row<_nRows);
   132     NSParameterAssert(col<_nColumns);
   133     unsigned index = row*_nColumns+col;
   134     GridCell *cell = [_cells objectAtIndex: index];
   135     if( (id)cell == [NSNull null] ) {
   136         CGRect frame = CGRectMake(col*_spacing.width, row*_spacing.height,
   137                                   _spacing.width,_spacing.height);
   138         cell = [self createCellAtRow: row column: col suggestedFrame: frame];
   139         if( cell ) {
   140             [_cells replaceObjectAtIndex: index withObject: cell];
   141             [self addSublayer: cell];
   142             [self setNeedsDisplay];
   143         }
   144     }
   145     return cell;
   146 }
   147 
   148 
   149 - (void) addAllCells
   150 {
   151     for( int row=_nRows-1; row>=0; row-- )                // makes 'upper' cells be in 'back'
   152         for( int col=0; col<_nColumns; col++ ) 
   153             [self addCellAtRow: row column: col];
   154 }
   155 
   156 
   157 - (void) removeCellAtRow: (unsigned)row column: (unsigned)col
   158 {
   159     NSParameterAssert(row<_nRows);
   160     NSParameterAssert(col<_nColumns);
   161     unsigned index = row*_nColumns+col;
   162     id cell = [_cells objectAtIndex: index];
   163     if( cell != [NSNull null] )
   164         [cell removeFromSuperlayer];
   165     [_cells replaceObjectAtIndex: index withObject: [NSNull null]];
   166     [self setNeedsDisplay];
   167 }
   168 
   169 
   170 #pragma mark -
   171 #pragma mark DRAWING:
   172 
   173 
   174 - (void) drawCellsInContext: (CGContextRef)ctx fill: (BOOL)fill
   175 {
   176     // Subroutine of -drawInContext:. Draws all the cells, with or without a fill.
   177     for( unsigned row=0; row<_nRows; row++ )
   178         for( unsigned col=0; col<_nColumns; col++ ) {
   179             GridCell *cell = [self cellAtRow: row column: col];
   180             if( cell )
   181                 [cell drawInParentContext: ctx fill: fill];
   182         }
   183 }
   184 
   185 
   186 - (void)drawInContext:(CGContextRef)ctx
   187 {
   188     // Custom CALayer drawing implementation. Delegates to the cells to draw themselves
   189     // in me; this is more efficient than having each cell have its own drawing.
   190     if( _cellColor ) {
   191         CGContextSetFillColorWithColor(ctx, _cellColor);
   192         [self drawCellsInContext: ctx fill: YES];
   193     }
   194     if( _lineColor ) {
   195         CGContextSetStrokeColorWithColor(ctx,_lineColor);
   196         [self drawCellsInContext:ctx fill: NO];
   197     }
   198 }
   199 
   200 
   201 @end
   202 
   203 
   204 
   205 #pragma mark -
   206 
   207 @implementation GridCell
   208 
   209 
   210 - (id) initWithGrid: (Grid*)grid 
   211                 row: (unsigned)row column: (unsigned)col
   212               frame: (CGRect)frame
   213 {
   214     self = [super init];
   215     if (self != nil) {
   216         _grid = grid;
   217         _row = row;
   218         _column = col;
   219         self.position = frame.origin;
   220         CGRect bounds = frame;
   221         bounds.origin.x -= floor(bounds.origin.x);  // make sure my coords fall on pixel boundaries
   222         bounds.origin.y -= floor(bounds.origin.y);
   223         self.bounds = bounds;
   224         self.anchorPoint = CGPointMake(0,0);
   225         self.borderColor = kHighlightColor;         // Used when highlighting (see -setHighlighted:)
   226     }
   227     return self;
   228 }
   229 
   230 - (NSString*) description
   231 {
   232     return [NSString stringWithFormat: @"%@(%u,%u)", [self class],_column,_row];
   233 }
   234 
   235 @synthesize grid=_grid, row=_row, column=_column;
   236 
   237 
   238 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
   239 {
   240     // Default implementation just fills or outlines the cell.
   241     CGRect frame = self.frame;
   242     if( fill )
   243         CGContextFillRect(ctx,frame);
   244     else
   245         CGContextStrokeRect(ctx, frame);
   246 }
   247 
   248 
   249 - (void) setBit: (Bit*)bit
   250 {
   251     if( bit != self.bit ) {
   252         [super setBit: bit];
   253         if( bit ) {
   254             // Center it:
   255             CGSize size = self.bounds.size;
   256             bit.position = CGPointMake(floor(size.width/2.0),
   257                                        floor(size.height/2.0));
   258         }
   259     }
   260 }
   261 
   262 - (Bit*) canDragBit: (Bit*)bit
   263 {
   264     if( _grid.allowsMoves && bit==self.bit )
   265         return [super canDragBit: bit];
   266     else
   267         return nil;
   268 }
   269 
   270 - (BOOL) canDropBit: (Bit*)bit atPoint: (CGPoint)point
   271 {
   272     return self.bit == nil || _grid.allowsCaptures;
   273 }
   274 
   275 
   276 - (BOOL) fwdIsN 
   277 {
   278     return self.game.currentPlayer.index == 0;
   279 }
   280 
   281 
   282 - (NSArray*) neighbors
   283 {
   284     BOOL orthogonal = ! _grid.usesDiagonals;
   285     NSMutableArray *neighbors = [NSMutableArray arrayWithCapacity: 8];
   286     for( int dy=-1; dy<=1; dy++ )
   287         for( int dx=-1; dx<=1; dx++ )
   288             if( (dx || dy) && !(orthogonal && dx && dy) ) {
   289                 GridCell *cell = [_grid cellAtRow: _row+dy column: _column+dx];
   290                 if( cell )
   291                     [neighbors addObject: cell];
   292             }
   293     return neighbors;
   294 }
   295 
   296 
   297 // Recursive subroutine used by getGroup:.
   298 - (void) x_addToGroup: (NSMutableSet*)group liberties: (NSMutableSet*)liberties owner: (Player*)owner
   299 {
   300     Bit *bit = self.bit;
   301     if( bit == nil ) {
   302         if( [liberties containsObject: self] )
   303             return; // already traversed
   304         [liberties addObject: self];
   305     } else if( bit.owner==owner ) {
   306         if( [group containsObject: self] )
   307             return; // already traversed
   308         [group addObject: self];
   309         for( GridCell *c in self.neighbors )
   310             [c x_addToGroup: group liberties: liberties owner: owner];
   311     }
   312 }
   313 
   314 
   315 - (NSSet*) getGroup: (int*)outLiberties
   316 {
   317     NSMutableSet *group=[NSMutableSet set], *liberties=nil;
   318     if( outLiberties )
   319         liberties = [NSMutableSet set];
   320     [self x_addToGroup: group liberties: liberties owner: self.bit.owner];
   321     if( outLiberties )
   322         *outLiberties = liberties.count;
   323     return group;
   324 }
   325 
   326 
   327 #pragma mark -
   328 #pragma mark DRAG-AND-DROP:
   329 
   330 
   331 // An image from another app can be dragged onto a Dispenser to change the Piece's appearance.
   332 
   333 
   334 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
   335 {
   336     NSPasteboard *pb = [sender draggingPasteboard];
   337     if( [NSImage canInitWithPasteboard: pb] )
   338         return NSDragOperationCopy;
   339     else
   340         return NSDragOperationNone;
   341 }
   342 
   343 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
   344 {
   345     CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard]);
   346     if( image ) {
   347         CGColorRef pattern = CreatePatternColor(image);
   348         _grid.cellColor = pattern;
   349         CGColorRelease(pattern);
   350         [_grid setNeedsDisplay];
   351         return YES;
   352     } else
   353         return NO;
   354 }
   355 
   356 
   357 @end
   358 
   359 
   360 
   361 
   362 #pragma mark -
   363 
   364 @implementation RectGrid
   365 
   366 
   367 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
   368             spacing: (CGSize)spacing
   369            position: (CGPoint)pos
   370 {
   371     self = [super initWithRows: nRows columns: nColumns spacing: spacing position: pos];
   372     if( self ) {
   373         _cellClass = [Square class];
   374     }
   375     return self;
   376 }
   377 
   378 
   379 - (CGColorRef) altCellColor                         {return _altCellColor;}
   380 - (void) setAltCellColor: (CGColorRef)altCellColor  {setcolor(&_altCellColor,altCellColor);}
   381 
   382 
   383 @end
   384 
   385 
   386 
   387 #pragma mark -
   388 
   389 @implementation Square
   390 
   391 
   392 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
   393 {
   394     if( fill ) {
   395         CGColorRef c = ((RectGrid*)_grid).altCellColor;
   396         if( c ) {
   397             if( ! ((_row+_column) & 1) )
   398                 c = _grid.cellColor;
   399             CGContextSetFillColorWithColor(ctx, c);
   400         }
   401     }
   402     [super drawInParentContext: ctx fill: fill];
   403 }
   404 
   405 
   406 - (void) setHighlighted: (BOOL)highlighted
   407 {
   408     [super setHighlighted: highlighted];
   409     self.borderWidth = (highlighted ?6 :0);
   410 }
   411 
   412 
   413 - (Square*) nw     {return (Square*)[_grid cellAtRow: _row+1 column: _column-1];}
   414 - (Square*) n      {return (Square*)[_grid cellAtRow: _row+1 column: _column  ];}
   415 - (Square*) ne     {return (Square*)[_grid cellAtRow: _row+1 column: _column+1];}
   416 - (Square*) e      {return (Square*)[_grid cellAtRow: _row   column: _column+1];}
   417 - (Square*) se     {return (Square*)[_grid cellAtRow: _row-1 column: _column+1];}
   418 - (Square*) s      {return (Square*)[_grid cellAtRow: _row-1 column: _column  ];}
   419 - (Square*) sw     {return (Square*)[_grid cellAtRow: _row-1 column: _column-1];}
   420 - (Square*) w      {return (Square*)[_grid cellAtRow: _row   column: _column-1];}
   421 
   422 // Directions relative to the current player:
   423 - (Square*) fl     {return self.fwdIsN ?self.nw :self.se;}
   424 - (Square*) f      {return self.fwdIsN ?self.n  :self.s;}
   425 - (Square*) fr     {return self.fwdIsN ?self.ne :self.sw;}
   426 - (Square*) r      {return self.fwdIsN ?self.e  :self.w;}
   427 - (Square*) br     {return self.fwdIsN ?self.se :self.nw;}
   428 - (Square*) b      {return self.fwdIsN ?self.s  :self.n;}
   429 - (Square*) bl     {return self.fwdIsN ?self.sw :self.ne;}
   430 - (Square*) l      {return self.fwdIsN ?self.w  :self.e;}
   431 
   432 
   433 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
   434 {
   435     CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard]);
   436     if( image ) {
   437         CGColorRef color = CreatePatternColor(image);
   438         RectGrid *rectGrid = (RectGrid*)_grid;
   439         if( rectGrid.altCellColor && ((_row+_column) & 1) )
   440             rectGrid.altCellColor = color;
   441         else
   442             rectGrid.cellColor = color;
   443         CGColorRelease(color);
   444         [rectGrid setNeedsDisplay];
   445         return YES;
   446     } else
   447         return NO;
   448 }
   449 
   450 @end
   451 
   452 
   453 
   454 #pragma mark -
   455 
   456 @implementation GoSquare
   457 
   458 @synthesize dotted=_dotted;
   459 
   460 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
   461 {
   462     if( fill )
   463         [super drawInParentContext: ctx fill: fill];
   464     else {
   465         CGRect frame = self.frame;
   466         const CGFloat midx=floor(CGRectGetMidX(frame))+0.5, 
   467                     midy=floor(CGRectGetMidY(frame))+0.5;
   468         CGPoint p[4] = {{CGRectGetMinX(frame),midy},
   469                         {CGRectGetMaxX(frame),midy},
   470                         {midx,CGRectGetMinY(frame)},
   471                         {midx,CGRectGetMaxY(frame)}};
   472         if( ! self.s )  p[2].y = midy;
   473         if( ! self.n )  p[3].y = midy;
   474         if( ! self.w )  p[0].x = midx;
   475         if( ! self.e )  p[1].x = midx;
   476         CGContextStrokeLineSegments(ctx, p, 4);
   477         
   478         if( _dotted ) {
   479             CGContextSetFillColorWithColor(ctx,_grid.lineColor);
   480             CGRect dot = CGRectMake(midx-2.5, midy-2.5, 5, 5);
   481             CGContextFillEllipseInRect(ctx, dot);
   482         }
   483     }
   484 }
   485 
   486 @end