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