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