Source/Grid.m
author Jens Alfke <jens@mooseyard.com>
Mon Mar 10 17:32:04 2008 -0700 (2008-03-10)
changeset 2 7b0441db81e5
parent 0 e9f7ba4718e1
child 3 40d225cf9c43
permissions -rw-r--r--
Oops, needed to fix an #include
     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         self.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 #if ! TARGET_OS_ASPEN
   332 
   333 // An image from another app can be dragged onto a Dispenser to change the Piece's appearance.
   334 
   335 
   336 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
   337 {
   338     NSPasteboard *pb = [sender draggingPasteboard];
   339     if( [NSImage canInitWithPasteboard: pb] )
   340         return NSDragOperationCopy;
   341     else
   342         return NSDragOperationNone;
   343 }
   344 
   345 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
   346 {
   347     CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard]);
   348     if( image ) {
   349         CGColorRef pattern = CreatePatternColor(image);
   350         _grid.cellColor = pattern;
   351         CGColorRelease(pattern);
   352         [_grid setNeedsDisplay];
   353         return YES;
   354     } else
   355         return NO;
   356 }
   357 
   358 #endif
   359 
   360 @end
   361 
   362 
   363 
   364 
   365 #pragma mark -
   366 
   367 @implementation RectGrid
   368 
   369 
   370 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
   371             spacing: (CGSize)spacing
   372            position: (CGPoint)pos
   373 {
   374     self = [super initWithRows: nRows columns: nColumns spacing: spacing position: pos];
   375     if( self ) {
   376         _cellClass = [Square class];
   377     }
   378     return self;
   379 }
   380 
   381 
   382 - (CGColorRef) altCellColor                         {return _altCellColor;}
   383 - (void) setAltCellColor: (CGColorRef)altCellColor  {setcolor(&_altCellColor,altCellColor);}
   384 
   385 
   386 @end
   387 
   388 
   389 
   390 #pragma mark -
   391 
   392 @implementation Square
   393 
   394 
   395 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
   396 {
   397     if( fill ) {
   398         CGColorRef c = ((RectGrid*)_grid).altCellColor;
   399         if( c ) {
   400             if( ! ((_row+_column) & 1) )
   401                 c = _grid.cellColor;
   402             CGContextSetFillColorWithColor(ctx, c);
   403         }
   404     }
   405     [super drawInParentContext: ctx fill: fill];
   406 }
   407 
   408 
   409 - (void) setHighlighted: (BOOL)highlighted
   410 {
   411     [super setHighlighted: highlighted];
   412     self.borderWidth = (highlighted ?6 :0);
   413 }
   414 
   415 
   416 - (Square*) nw     {return (Square*)[_grid cellAtRow: _row+1 column: _column-1];}
   417 - (Square*) n      {return (Square*)[_grid cellAtRow: _row+1 column: _column  ];}
   418 - (Square*) ne     {return (Square*)[_grid cellAtRow: _row+1 column: _column+1];}
   419 - (Square*) e      {return (Square*)[_grid cellAtRow: _row   column: _column+1];}
   420 - (Square*) se     {return (Square*)[_grid cellAtRow: _row-1 column: _column+1];}
   421 - (Square*) s      {return (Square*)[_grid cellAtRow: _row-1 column: _column  ];}
   422 - (Square*) sw     {return (Square*)[_grid cellAtRow: _row-1 column: _column-1];}
   423 - (Square*) w      {return (Square*)[_grid cellAtRow: _row   column: _column-1];}
   424 
   425 // Directions relative to the current player:
   426 - (Square*) fl     {return self.fwdIsN ?self.nw :self.se;}
   427 - (Square*) f      {return self.fwdIsN ?self.n  :self.s;}
   428 - (Square*) fr     {return self.fwdIsN ?self.ne :self.sw;}
   429 - (Square*) r      {return self.fwdIsN ?self.e  :self.w;}
   430 - (Square*) br     {return self.fwdIsN ?self.se :self.nw;}
   431 - (Square*) b      {return self.fwdIsN ?self.s  :self.n;}
   432 - (Square*) bl     {return self.fwdIsN ?self.sw :self.ne;}
   433 - (Square*) l      {return self.fwdIsN ?self.w  :self.e;}
   434 
   435 
   436 #if ! TARGET_OS_ASPEN
   437 
   438 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
   439 {
   440     CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard]);
   441     if( image ) {
   442         CGColorRef color = CreatePatternColor(image);
   443         RectGrid *rectGrid = (RectGrid*)_grid;
   444         if( rectGrid.altCellColor && ((_row+_column) & 1) )
   445             rectGrid.altCellColor = color;
   446         else
   447             rectGrid.cellColor = color;
   448         CGColorRelease(color);
   449         [rectGrid setNeedsDisplay];
   450         return YES;
   451     } else
   452         return NO;
   453 }
   454 
   455 #endif
   456 
   457 @end
   458 
   459 
   460 
   461 #pragma mark -
   462 
   463 @implementation GoSquare
   464 
   465 @synthesize dotted=_dotted;
   466 
   467 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
   468 {
   469     if( fill )
   470         [super drawInParentContext: ctx fill: fill];
   471     else {
   472         CGRect frame = self.frame;
   473         const CGFloat midx=floor(CGRectGetMidX(frame))+0.5, 
   474                     midy=floor(CGRectGetMidY(frame))+0.5;
   475         CGPoint p[4] = {{CGRectGetMinX(frame),midy},
   476                         {CGRectGetMaxX(frame),midy},
   477                         {midx,CGRectGetMinY(frame)},
   478                         {midx,CGRectGetMaxY(frame)}};
   479         if( ! self.s )  p[2].y = midy;
   480         if( ! self.n )  p[3].y = midy;
   481         if( ! self.w )  p[0].x = midx;
   482         if( ! self.e )  p[1].x = midx;
   483         CGContextStrokeLineSegments(ctx, p, 4);
   484         
   485         if( _dotted ) {
   486             CGContextSetFillColorWithColor(ctx,_grid.lineColor);
   487             CGRect dot = CGRectMake(midx-2.5, midy-2.5, 5, 5);
   488             CGContextFillEllipseInRect(ctx, dot);
   489         }
   490     }
   491 }
   492 
   493 
   494 @end