Source/Grid.m
changeset 0 e9f7ba4718e1
child 1 3eb7be1dd7b6
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/Source/Grid.m	Fri Mar 07 11:43:02 2008 -0800
     1.3 @@ -0,0 +1,486 @@
     1.4 +/*  This code is based on Apple's "GeekGameBoard" sample code, version 1.0.
     1.5 +    http://developer.apple.com/samplecode/GeekGameBoard/
     1.6 +    Copyright © 2007 Apple Inc. Copyright © 2008 Jens Alfke. All Rights Reserved.
     1.7 +
     1.8 +    Redistribution and use in source and binary forms, with or without modification, are permitted
     1.9 +    provided that the following conditions are met:
    1.10 +
    1.11 +    * Redistributions of source code must retain the above copyright notice, this list of conditions
    1.12 +      and the following disclaimer.
    1.13 +    * Redistributions in binary form must reproduce the above copyright notice, this list of
    1.14 +      conditions and the following disclaimer in the documentation and/or other materials provided
    1.15 +      with the distribution.
    1.16 +
    1.17 +    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
    1.18 +    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
    1.19 +    FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
    1.20 +    BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    1.21 +    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
    1.22 +    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
    1.23 +    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
    1.24 +    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    1.25 +*/
    1.26 +#import "Grid.h"
    1.27 +#import "Bit.h"
    1.28 +#import "Game.h"
    1.29 +#import "QuartzUtils.h"
    1.30 +
    1.31 +
    1.32 +@implementation Grid
    1.33 +
    1.34 +
    1.35 +- (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
    1.36 +            spacing: (CGSize)spacing
    1.37 +           position: (CGPoint)pos
    1.38 +{
    1.39 +    NSParameterAssert(nRows>0 && nColumns>0);
    1.40 +    self = [super init];
    1.41 +    if( self ) {
    1.42 +        _nRows = nRows;
    1.43 +        _nColumns = nColumns;
    1.44 +        _spacing = spacing;
    1.45 +        _cellClass = [GridCell class];
    1.46 +        _lineColor = kBlackColor;
    1.47 +        _allowsMoves = YES;
    1.48 +        _usesDiagonals = YES;
    1.49 +
    1.50 +        self.bounds = CGRectMake(-1, -1, nColumns*spacing.width+2, nRows*spacing.height+2);
    1.51 +        self.position = pos;
    1.52 +        self.anchorPoint = CGPointMake(0,0);
    1.53 +        self.zPosition = kBoardZ;
    1.54 +        self.needsDisplayOnBoundsChange = YES;
    1.55 +        
    1.56 +        unsigned n = nRows*nColumns;
    1.57 +        _cells = [[NSMutableArray alloc] initWithCapacity: n];
    1.58 +        id null = [NSNull null];
    1.59 +        while( n-- > 0 )
    1.60 +            [_cells addObject: null];
    1.61 +
    1.62 +        [self setNeedsDisplay];
    1.63 +    }
    1.64 +    return self;
    1.65 +}
    1.66 +
    1.67 +
    1.68 +- (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
    1.69 +              frame: (CGRect)frame
    1.70 +{
    1.71 +    CGFloat spacing = floor(MIN( (frame.size.width -2)/(CGFloat)nColumns,
    1.72 +                               (frame.size.height-2)/(CGFloat)nRows) );
    1.73 +    return [self initWithRows: nRows columns: nColumns
    1.74 +                      spacing: CGSizeMake(spacing,spacing)
    1.75 +                     position: frame.origin];
    1.76 +}
    1.77 +
    1.78 +
    1.79 +- (void) dealloc
    1.80 +{
    1.81 +    CGColorRelease(_cellColor);
    1.82 +    CGColorRelease(_lineColor);
    1.83 +    [_cells release];
    1.84 +    [super dealloc];
    1.85 +}
    1.86 +
    1.87 +
    1.88 +static void setcolor( CGColorRef *var, CGColorRef color )
    1.89 +{
    1.90 +    if( color != *var ) {
    1.91 +        CGColorRelease(*var);
    1.92 +        *var = CGColorRetain(color);
    1.93 +    }
    1.94 +}
    1.95 +
    1.96 +- (CGColorRef) cellColor                        {return _cellColor;}
    1.97 +- (void) setCellColor: (CGColorRef)cellColor    {setcolor(&_cellColor,cellColor);}
    1.98 +
    1.99 +- (CGColorRef) lineColor                        {return _lineColor;}
   1.100 +- (void) setLineColor: (CGColorRef)lineColor    {setcolor(&_lineColor,lineColor);}
   1.101 +
   1.102 +@synthesize cellClass=_cellClass, rows=_nRows, columns=_nColumns, spacing=_spacing,
   1.103 +            usesDiagonals=_usesDiagonals, allowsMoves=_allowsMoves, allowsCaptures=_allowsCaptures;
   1.104 +
   1.105 +
   1.106 +#pragma mark -
   1.107 +#pragma mark GEOMETRY:
   1.108 +
   1.109 +
   1.110 +- (GridCell*) cellAtRow: (unsigned)row column: (unsigned)col
   1.111 +{
   1.112 +    if( row < _nRows && col < _nColumns ) {
   1.113 +        id cell = [_cells objectAtIndex: row*_nColumns+col];
   1.114 +        if( cell != [NSNull null] )
   1.115 +            return cell;
   1.116 +    }
   1.117 +    return nil;
   1.118 +}
   1.119 +
   1.120 +
   1.121 +/** Subclasses can override this, to change the cell's class or frame. */
   1.122 +- (GridCell*) createCellAtRow: (unsigned)row column: (unsigned)col 
   1.123 +               suggestedFrame: (CGRect)frame
   1.124 +{
   1.125 +    return [[[_cellClass alloc] initWithGrid: self 
   1.126 +                                        row: row column: col
   1.127 +                                      frame: frame]
   1.128 +                    autorelease];
   1.129 +}
   1.130 +
   1.131 +
   1.132 +- (GridCell*) addCellAtRow: (unsigned)row column: (unsigned)col
   1.133 +{
   1.134 +    NSParameterAssert(row<_nRows);
   1.135 +    NSParameterAssert(col<_nColumns);
   1.136 +    unsigned index = row*_nColumns+col;
   1.137 +    GridCell *cell = [_cells objectAtIndex: index];
   1.138 +    if( (id)cell == [NSNull null] ) {
   1.139 +        CGRect frame = CGRectMake(col*_spacing.width, row*_spacing.height,
   1.140 +                                  _spacing.width,_spacing.height);
   1.141 +        cell = [self createCellAtRow: row column: col suggestedFrame: frame];
   1.142 +        if( cell ) {
   1.143 +            [_cells replaceObjectAtIndex: index withObject: cell];
   1.144 +            [self addSublayer: cell];
   1.145 +            [self setNeedsDisplay];
   1.146 +        }
   1.147 +    }
   1.148 +    return cell;
   1.149 +}
   1.150 +
   1.151 +
   1.152 +- (void) addAllCells
   1.153 +{
   1.154 +    for( int row=_nRows-1; row>=0; row-- )                // makes 'upper' cells be in 'back'
   1.155 +        for( int col=0; col<_nColumns; col++ ) 
   1.156 +            [self addCellAtRow: row column: col];
   1.157 +}
   1.158 +
   1.159 +
   1.160 +- (void) removeCellAtRow: (unsigned)row column: (unsigned)col
   1.161 +{
   1.162 +    NSParameterAssert(row<_nRows);
   1.163 +    NSParameterAssert(col<_nColumns);
   1.164 +    unsigned index = row*_nColumns+col;
   1.165 +    id cell = [_cells objectAtIndex: index];
   1.166 +    if( cell != [NSNull null] )
   1.167 +        [cell removeFromSuperlayer];
   1.168 +    [_cells replaceObjectAtIndex: index withObject: [NSNull null]];
   1.169 +    [self setNeedsDisplay];
   1.170 +}
   1.171 +
   1.172 +
   1.173 +#pragma mark -
   1.174 +#pragma mark DRAWING:
   1.175 +
   1.176 +
   1.177 +- (void) drawCellsInContext: (CGContextRef)ctx fill: (BOOL)fill
   1.178 +{
   1.179 +    // Subroutine of -drawInContext:. Draws all the cells, with or without a fill.
   1.180 +    for( unsigned row=0; row<_nRows; row++ )
   1.181 +        for( unsigned col=0; col<_nColumns; col++ ) {
   1.182 +            GridCell *cell = [self cellAtRow: row column: col];
   1.183 +            if( cell )
   1.184 +                [cell drawInParentContext: ctx fill: fill];
   1.185 +        }
   1.186 +}
   1.187 +
   1.188 +
   1.189 +- (void)drawInContext:(CGContextRef)ctx
   1.190 +{
   1.191 +    // Custom CALayer drawing implementation. Delegates to the cells to draw themselves
   1.192 +    // in me; this is more efficient than having each cell have its own drawing.
   1.193 +    if( _cellColor ) {
   1.194 +        CGContextSetFillColorWithColor(ctx, _cellColor);
   1.195 +        [self drawCellsInContext: ctx fill: YES];
   1.196 +    }
   1.197 +    if( _lineColor ) {
   1.198 +        CGContextSetStrokeColorWithColor(ctx,_lineColor);
   1.199 +        [self drawCellsInContext:ctx fill: NO];
   1.200 +    }
   1.201 +}
   1.202 +
   1.203 +
   1.204 +@end
   1.205 +
   1.206 +
   1.207 +
   1.208 +#pragma mark -
   1.209 +
   1.210 +@implementation GridCell
   1.211 +
   1.212 +
   1.213 +- (id) initWithGrid: (Grid*)grid 
   1.214 +                row: (unsigned)row column: (unsigned)col
   1.215 +              frame: (CGRect)frame
   1.216 +{
   1.217 +    self = [super init];
   1.218 +    if (self != nil) {
   1.219 +        _grid = grid;
   1.220 +        _row = row;
   1.221 +        _column = col;
   1.222 +        self.position = frame.origin;
   1.223 +        CGRect bounds = frame;
   1.224 +        bounds.origin.x -= floor(bounds.origin.x);  // make sure my coords fall on pixel boundaries
   1.225 +        bounds.origin.y -= floor(bounds.origin.y);
   1.226 +        self.bounds = bounds;
   1.227 +        self.anchorPoint = CGPointMake(0,0);
   1.228 +        self.borderColor = kHighlightColor;         // Used when highlighting (see -setHighlighted:)
   1.229 +    }
   1.230 +    return self;
   1.231 +}
   1.232 +
   1.233 +- (NSString*) description
   1.234 +{
   1.235 +    return [NSString stringWithFormat: @"%@(%u,%u)", [self class],_column,_row];
   1.236 +}
   1.237 +
   1.238 +@synthesize grid=_grid, row=_row, column=_column;
   1.239 +
   1.240 +
   1.241 +- (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
   1.242 +{
   1.243 +    // Default implementation just fills or outlines the cell.
   1.244 +    CGRect frame = self.frame;
   1.245 +    if( fill )
   1.246 +        CGContextFillRect(ctx,frame);
   1.247 +    else
   1.248 +        CGContextStrokeRect(ctx, frame);
   1.249 +}
   1.250 +
   1.251 +
   1.252 +- (void) setBit: (Bit*)bit
   1.253 +{
   1.254 +    if( bit != self.bit ) {
   1.255 +        [super setBit: bit];
   1.256 +        if( bit ) {
   1.257 +            // Center it:
   1.258 +            CGSize size = self.bounds.size;
   1.259 +            bit.position = CGPointMake(floor(size.width/2.0),
   1.260 +                                       floor(size.height/2.0));
   1.261 +        }
   1.262 +    }
   1.263 +}
   1.264 +
   1.265 +- (Bit*) canDragBit: (Bit*)bit
   1.266 +{
   1.267 +    if( _grid.allowsMoves && bit==self.bit )
   1.268 +        return [super canDragBit: bit];
   1.269 +    else
   1.270 +        return nil;
   1.271 +}
   1.272 +
   1.273 +- (BOOL) canDropBit: (Bit*)bit atPoint: (CGPoint)point
   1.274 +{
   1.275 +    return self.bit == nil || _grid.allowsCaptures;
   1.276 +}
   1.277 +
   1.278 +
   1.279 +- (BOOL) fwdIsN 
   1.280 +{
   1.281 +    return self.game.currentPlayer.index == 0;
   1.282 +}
   1.283 +
   1.284 +
   1.285 +- (NSArray*) neighbors
   1.286 +{
   1.287 +    BOOL orthogonal = ! _grid.usesDiagonals;
   1.288 +    NSMutableArray *neighbors = [NSMutableArray arrayWithCapacity: 8];
   1.289 +    for( int dy=-1; dy<=1; dy++ )
   1.290 +        for( int dx=-1; dx<=1; dx++ )
   1.291 +            if( (dx || dy) && !(orthogonal && dx && dy) ) {
   1.292 +                GridCell *cell = [_grid cellAtRow: _row+dy column: _column+dx];
   1.293 +                if( cell )
   1.294 +                    [neighbors addObject: cell];
   1.295 +            }
   1.296 +    return neighbors;
   1.297 +}
   1.298 +
   1.299 +
   1.300 +// Recursive subroutine used by getGroup:.
   1.301 +- (void) x_addToGroup: (NSMutableSet*)group liberties: (NSMutableSet*)liberties owner: (Player*)owner
   1.302 +{
   1.303 +    Bit *bit = self.bit;
   1.304 +    if( bit == nil ) {
   1.305 +        if( [liberties containsObject: self] )
   1.306 +            return; // already traversed
   1.307 +        [liberties addObject: self];
   1.308 +    } else if( bit.owner==owner ) {
   1.309 +        if( [group containsObject: self] )
   1.310 +            return; // already traversed
   1.311 +        [group addObject: self];
   1.312 +        for( GridCell *c in self.neighbors )
   1.313 +            [c x_addToGroup: group liberties: liberties owner: owner];
   1.314 +    }
   1.315 +}
   1.316 +
   1.317 +
   1.318 +- (NSSet*) getGroup: (int*)outLiberties
   1.319 +{
   1.320 +    NSMutableSet *group=[NSMutableSet set], *liberties=nil;
   1.321 +    if( outLiberties )
   1.322 +        liberties = [NSMutableSet set];
   1.323 +    [self x_addToGroup: group liberties: liberties owner: self.bit.owner];
   1.324 +    if( outLiberties )
   1.325 +        *outLiberties = liberties.count;
   1.326 +    return group;
   1.327 +}
   1.328 +
   1.329 +
   1.330 +#pragma mark -
   1.331 +#pragma mark DRAG-AND-DROP:
   1.332 +
   1.333 +
   1.334 +// An image from another app can be dragged onto a Dispenser to change the Piece's appearance.
   1.335 +
   1.336 +
   1.337 +- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
   1.338 +{
   1.339 +    NSPasteboard *pb = [sender draggingPasteboard];
   1.340 +    if( [NSImage canInitWithPasteboard: pb] )
   1.341 +        return NSDragOperationCopy;
   1.342 +    else
   1.343 +        return NSDragOperationNone;
   1.344 +}
   1.345 +
   1.346 +- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
   1.347 +{
   1.348 +    CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard]);
   1.349 +    if( image ) {
   1.350 +        CGColorRef pattern = CreatePatternColor(image);
   1.351 +        _grid.cellColor = pattern;
   1.352 +        CGColorRelease(pattern);
   1.353 +        [_grid setNeedsDisplay];
   1.354 +        return YES;
   1.355 +    } else
   1.356 +        return NO;
   1.357 +}
   1.358 +
   1.359 +
   1.360 +@end
   1.361 +
   1.362 +
   1.363 +
   1.364 +
   1.365 +#pragma mark -
   1.366 +
   1.367 +@implementation RectGrid
   1.368 +
   1.369 +
   1.370 +- (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
   1.371 +            spacing: (CGSize)spacing
   1.372 +           position: (CGPoint)pos
   1.373 +{
   1.374 +    self = [super initWithRows: nRows columns: nColumns spacing: spacing position: pos];
   1.375 +    if( self ) {
   1.376 +        _cellClass = [Square class];
   1.377 +    }
   1.378 +    return self;
   1.379 +}
   1.380 +
   1.381 +
   1.382 +- (CGColorRef) altCellColor                         {return _altCellColor;}
   1.383 +- (void) setAltCellColor: (CGColorRef)altCellColor  {setcolor(&_altCellColor,altCellColor);}
   1.384 +
   1.385 +
   1.386 +@end
   1.387 +
   1.388 +
   1.389 +
   1.390 +#pragma mark -
   1.391 +
   1.392 +@implementation Square
   1.393 +
   1.394 +
   1.395 +- (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
   1.396 +{
   1.397 +    if( fill ) {
   1.398 +        CGColorRef c = ((RectGrid*)_grid).altCellColor;
   1.399 +        if( c ) {
   1.400 +            if( ! ((_row+_column) & 1) )
   1.401 +                c = _grid.cellColor;
   1.402 +            CGContextSetFillColorWithColor(ctx, c);
   1.403 +        }
   1.404 +    }
   1.405 +    [super drawInParentContext: ctx fill: fill];
   1.406 +}
   1.407 +
   1.408 +
   1.409 +- (void) setHighlighted: (BOOL)highlighted
   1.410 +{
   1.411 +    [super setHighlighted: highlighted];
   1.412 +    self.borderWidth = (highlighted ?6 :0);
   1.413 +}
   1.414 +
   1.415 +
   1.416 +- (Square*) nw     {return (Square*)[_grid cellAtRow: _row+1 column: _column-1];}
   1.417 +- (Square*) n      {return (Square*)[_grid cellAtRow: _row+1 column: _column  ];}
   1.418 +- (Square*) ne     {return (Square*)[_grid cellAtRow: _row+1 column: _column+1];}
   1.419 +- (Square*) e      {return (Square*)[_grid cellAtRow: _row   column: _column+1];}
   1.420 +- (Square*) se     {return (Square*)[_grid cellAtRow: _row-1 column: _column+1];}
   1.421 +- (Square*) s      {return (Square*)[_grid cellAtRow: _row-1 column: _column  ];}
   1.422 +- (Square*) sw     {return (Square*)[_grid cellAtRow: _row-1 column: _column-1];}
   1.423 +- (Square*) w      {return (Square*)[_grid cellAtRow: _row   column: _column-1];}
   1.424 +
   1.425 +// Directions relative to the current player:
   1.426 +- (Square*) fl     {return self.fwdIsN ?self.nw :self.se;}
   1.427 +- (Square*) f      {return self.fwdIsN ?self.n  :self.s;}
   1.428 +- (Square*) fr     {return self.fwdIsN ?self.ne :self.sw;}
   1.429 +- (Square*) r      {return self.fwdIsN ?self.e  :self.w;}
   1.430 +- (Square*) br     {return self.fwdIsN ?self.se :self.nw;}
   1.431 +- (Square*) b      {return self.fwdIsN ?self.s  :self.n;}
   1.432 +- (Square*) bl     {return self.fwdIsN ?self.sw :self.ne;}
   1.433 +- (Square*) l      {return self.fwdIsN ?self.w  :self.e;}
   1.434 +
   1.435 +
   1.436 +- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
   1.437 +{
   1.438 +    CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard]);
   1.439 +    if( image ) {
   1.440 +        CGColorRef color = CreatePatternColor(image);
   1.441 +        RectGrid *rectGrid = (RectGrid*)_grid;
   1.442 +        if( rectGrid.altCellColor && ((_row+_column) & 1) )
   1.443 +            rectGrid.altCellColor = color;
   1.444 +        else
   1.445 +            rectGrid.cellColor = color;
   1.446 +        CGColorRelease(color);
   1.447 +        [rectGrid setNeedsDisplay];
   1.448 +        return YES;
   1.449 +    } else
   1.450 +        return NO;
   1.451 +}
   1.452 +
   1.453 +@end
   1.454 +
   1.455 +
   1.456 +
   1.457 +#pragma mark -
   1.458 +
   1.459 +@implementation GoSquare
   1.460 +
   1.461 +@synthesize dotted=_dotted;
   1.462 +
   1.463 +- (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
   1.464 +{
   1.465 +    if( fill )
   1.466 +        [super drawInParentContext: ctx fill: fill];
   1.467 +    else {
   1.468 +        CGRect frame = self.frame;
   1.469 +        const CGFloat midx=floor(CGRectGetMidX(frame))+0.5, 
   1.470 +                    midy=floor(CGRectGetMidY(frame))+0.5;
   1.471 +        CGPoint p[4] = {{CGRectGetMinX(frame),midy},
   1.472 +                        {CGRectGetMaxX(frame),midy},
   1.473 +                        {midx,CGRectGetMinY(frame)},
   1.474 +                        {midx,CGRectGetMaxY(frame)}};
   1.475 +        if( ! self.s )  p[2].y = midy;
   1.476 +        if( ! self.n )  p[3].y = midy;
   1.477 +        if( ! self.w )  p[0].x = midx;
   1.478 +        if( ! self.e )  p[1].x = midx;
   1.479 +        CGContextStrokeLineSegments(ctx, p, 4);
   1.480 +        
   1.481 +        if( _dotted ) {
   1.482 +            CGContextSetFillColorWithColor(ctx,_grid.lineColor);
   1.483 +            CGRect dot = CGRectMake(midx-2.5, midy-2.5, 5, 5);
   1.484 +            CGContextFillEllipseInRect(ctx, dot);
   1.485 +        }
   1.486 +    }
   1.487 +}
   1.488 +
   1.489 +@end