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