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