jens@0: /* This code is based on Apple's "GeekGameBoard" sample code, version 1.0. jens@0: http://developer.apple.com/samplecode/GeekGameBoard/ jens@0: Copyright © 2007 Apple Inc. Copyright © 2008 Jens Alfke. All Rights Reserved. jens@0: jens@0: Redistribution and use in source and binary forms, with or without modification, are permitted jens@0: provided that the following conditions are met: jens@0: jens@0: * Redistributions of source code must retain the above copyright notice, this list of conditions jens@0: and the following disclaimer. jens@0: * Redistributions in binary form must reproduce the above copyright notice, this list of jens@0: conditions and the following disclaimer in the documentation and/or other materials provided jens@0: with the distribution. jens@0: jens@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR jens@0: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND jens@0: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- jens@0: BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES jens@0: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR jens@0: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN jens@0: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF jens@0: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jens@0: */ jens@0: #import "Grid.h" jens@0: #import "Bit.h" jens@0: #import "Game.h" jens@0: #import "QuartzUtils.h" jens@0: jens@0: jens@0: @implementation Grid jens@0: jens@0: jens@0: - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns jens@0: spacing: (CGSize)spacing jens@0: position: (CGPoint)pos jens@0: { jens@0: NSParameterAssert(nRows>0 && nColumns>0); jens@0: self = [super init]; jens@0: if( self ) { jens@0: _nRows = nRows; jens@0: _nColumns = nColumns; jens@0: _spacing = spacing; jens@0: _cellClass = [GridCell class]; jens@1: self.lineColor = kBlackColor; jens@0: _allowsMoves = YES; jens@0: _usesDiagonals = YES; jens@0: jens@0: self.bounds = CGRectMake(-1, -1, nColumns*spacing.width+2, nRows*spacing.height+2); jens@0: self.position = pos; jens@0: self.anchorPoint = CGPointMake(0,0); jens@0: self.zPosition = kBoardZ; jens@0: self.needsDisplayOnBoundsChange = YES; jens@0: jens@0: unsigned n = nRows*nColumns; jens@0: _cells = [[NSMutableArray alloc] initWithCapacity: n]; jens@0: id null = [NSNull null]; jens@0: while( n-- > 0 ) jens@0: [_cells addObject: null]; jens@0: jens@0: [self setNeedsDisplay]; jens@0: } jens@0: return self; jens@0: } jens@0: jens@0: jens@0: - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns jens@0: frame: (CGRect)frame jens@0: { jens@0: CGFloat spacing = floor(MIN( (frame.size.width -2)/(CGFloat)nColumns, jens@0: (frame.size.height-2)/(CGFloat)nRows) ); jens@0: return [self initWithRows: nRows columns: nColumns jens@0: spacing: CGSizeMake(spacing,spacing) jens@0: position: frame.origin]; jens@0: } jens@0: jens@0: jens@0: - (void) dealloc jens@0: { jens@0: CGColorRelease(_cellColor); jens@0: CGColorRelease(_lineColor); jens@0: [_cells release]; jens@0: [super dealloc]; jens@0: } jens@0: jens@0: jens@0: static void setcolor( CGColorRef *var, CGColorRef color ) jens@0: { jens@0: if( color != *var ) { jens@0: CGColorRelease(*var); jens@0: *var = CGColorRetain(color); jens@0: } jens@0: } jens@0: jens@0: - (CGColorRef) cellColor {return _cellColor;} jens@0: - (void) setCellColor: (CGColorRef)cellColor {setcolor(&_cellColor,cellColor);} jens@0: jens@0: - (CGColorRef) lineColor {return _lineColor;} jens@0: - (void) setLineColor: (CGColorRef)lineColor {setcolor(&_lineColor,lineColor);} jens@0: jens@0: @synthesize cellClass=_cellClass, rows=_nRows, columns=_nColumns, spacing=_spacing, jens@0: usesDiagonals=_usesDiagonals, allowsMoves=_allowsMoves, allowsCaptures=_allowsCaptures; jens@0: jens@0: jens@0: #pragma mark - jens@0: #pragma mark GEOMETRY: jens@0: jens@0: jens@0: - (GridCell*) cellAtRow: (unsigned)row column: (unsigned)col jens@0: { jens@0: if( row < _nRows && col < _nColumns ) { jens@0: id cell = [_cells objectAtIndex: row*_nColumns+col]; jens@0: if( cell != [NSNull null] ) jens@0: return cell; jens@0: } jens@0: return nil; jens@0: } jens@0: jens@0: jens@0: /** Subclasses can override this, to change the cell's class or frame. */ jens@0: - (GridCell*) createCellAtRow: (unsigned)row column: (unsigned)col jens@0: suggestedFrame: (CGRect)frame jens@0: { jens@0: return [[[_cellClass alloc] initWithGrid: self jens@0: row: row column: col jens@0: frame: frame] jens@0: autorelease]; jens@0: } jens@0: jens@0: jens@0: - (GridCell*) addCellAtRow: (unsigned)row column: (unsigned)col jens@0: { jens@0: NSParameterAssert(row<_nRows); jens@0: NSParameterAssert(col<_nColumns); jens@0: unsigned index = row*_nColumns+col; jens@0: GridCell *cell = [_cells objectAtIndex: index]; jens@0: if( (id)cell == [NSNull null] ) { jens@0: CGRect frame = CGRectMake(col*_spacing.width, row*_spacing.height, jens@0: _spacing.width,_spacing.height); jens@0: cell = [self createCellAtRow: row column: col suggestedFrame: frame]; jens@0: if( cell ) { jens@0: [_cells replaceObjectAtIndex: index withObject: cell]; jens@0: [self addSublayer: cell]; jens@0: [self setNeedsDisplay]; jens@0: } jens@0: } jens@0: return cell; jens@0: } jens@0: jens@0: jens@0: - (void) addAllCells jens@0: { jens@0: for( int row=_nRows-1; row>=0; row-- ) // makes 'upper' cells be in 'back' jens@0: for( int col=0; col<_nColumns; col++ ) jens@0: [self addCellAtRow: row column: col]; jens@0: } jens@0: jens@0: jens@0: - (void) removeCellAtRow: (unsigned)row column: (unsigned)col jens@0: { jens@0: NSParameterAssert(row<_nRows); jens@0: NSParameterAssert(col<_nColumns); jens@0: unsigned index = row*_nColumns+col; jens@0: id cell = [_cells objectAtIndex: index]; jens@0: if( cell != [NSNull null] ) jens@0: [cell removeFromSuperlayer]; jens@0: [_cells replaceObjectAtIndex: index withObject: [NSNull null]]; jens@0: [self setNeedsDisplay]; jens@0: } jens@0: jens@0: jens@0: #pragma mark - jens@0: #pragma mark DRAWING: jens@0: jens@0: jens@0: - (void) drawCellsInContext: (CGContextRef)ctx fill: (BOOL)fill jens@0: { jens@0: // Subroutine of -drawInContext:. Draws all the cells, with or without a fill. jens@0: for( unsigned row=0; row<_nRows; row++ ) jens@0: for( unsigned col=0; col<_nColumns; col++ ) { jens@0: GridCell *cell = [self cellAtRow: row column: col]; jens@0: if( cell ) jens@0: [cell drawInParentContext: ctx fill: fill]; jens@0: } jens@0: } jens@0: jens@0: jens@0: - (void)drawInContext:(CGContextRef)ctx jens@0: { jens@0: // Custom CALayer drawing implementation. Delegates to the cells to draw themselves jens@0: // in me; this is more efficient than having each cell have its own drawing. jens@0: if( _cellColor ) { jens@0: CGContextSetFillColorWithColor(ctx, _cellColor); jens@0: [self drawCellsInContext: ctx fill: YES]; jens@0: } jens@0: if( _lineColor ) { jens@0: CGContextSetStrokeColorWithColor(ctx,_lineColor); jens@0: [self drawCellsInContext:ctx fill: NO]; jens@0: } jens@0: } jens@0: jens@0: jens@0: @end jens@0: jens@0: jens@0: jens@0: #pragma mark - jens@0: jens@0: @implementation GridCell jens@0: jens@0: jens@0: - (id) initWithGrid: (Grid*)grid jens@0: row: (unsigned)row column: (unsigned)col jens@0: frame: (CGRect)frame jens@0: { jens@0: self = [super init]; jens@0: if (self != nil) { jens@0: _grid = grid; jens@0: _row = row; jens@0: _column = col; jens@0: self.position = frame.origin; jens@0: CGRect bounds = frame; jens@0: bounds.origin.x -= floor(bounds.origin.x); // make sure my coords fall on pixel boundaries jens@0: bounds.origin.y -= floor(bounds.origin.y); jens@0: self.bounds = bounds; jens@0: self.anchorPoint = CGPointMake(0,0); jens@0: self.borderColor = kHighlightColor; // Used when highlighting (see -setHighlighted:) jens@0: } jens@0: return self; jens@0: } jens@0: jens@0: - (NSString*) description jens@0: { jens@0: return [NSString stringWithFormat: @"%@(%u,%u)", [self class],_column,_row]; jens@0: } jens@0: jens@0: @synthesize grid=_grid, row=_row, column=_column; jens@0: jens@0: jens@0: - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill jens@0: { jens@0: // Default implementation just fills or outlines the cell. jens@0: CGRect frame = self.frame; jens@0: if( fill ) jens@0: CGContextFillRect(ctx,frame); jens@0: else jens@0: CGContextStrokeRect(ctx, frame); jens@0: } jens@0: jens@0: jens@0: - (void) setBit: (Bit*)bit jens@0: { jens@0: if( bit != self.bit ) { jens@0: [super setBit: bit]; jens@0: if( bit ) { jens@0: // Center it: jens@0: CGSize size = self.bounds.size; jens@0: bit.position = CGPointMake(floor(size.width/2.0), jens@0: floor(size.height/2.0)); jens@0: } jens@0: } jens@0: } jens@0: jens@0: - (Bit*) canDragBit: (Bit*)bit jens@0: { jens@0: if( _grid.allowsMoves && bit==self.bit ) jens@0: return [super canDragBit: bit]; jens@0: else jens@0: return nil; jens@0: } jens@0: jens@0: - (BOOL) canDropBit: (Bit*)bit atPoint: (CGPoint)point jens@0: { jens@0: return self.bit == nil || _grid.allowsCaptures; jens@0: } jens@0: jens@0: jens@0: - (BOOL) fwdIsN jens@0: { jens@0: return self.game.currentPlayer.index == 0; jens@0: } jens@0: jens@0: jens@0: - (NSArray*) neighbors jens@0: { jens@0: BOOL orthogonal = ! _grid.usesDiagonals; jens@0: NSMutableArray *neighbors = [NSMutableArray arrayWithCapacity: 8]; jens@0: for( int dy=-1; dy<=1; dy++ ) jens@0: for( int dx=-1; dx<=1; dx++ ) jens@0: if( (dx || dy) && !(orthogonal && dx && dy) ) { jens@0: GridCell *cell = [_grid cellAtRow: _row+dy column: _column+dx]; jens@0: if( cell ) jens@0: [neighbors addObject: cell]; jens@0: } jens@0: return neighbors; jens@0: } jens@0: jens@0: jens@0: // Recursive subroutine used by getGroup:. jens@0: - (void) x_addToGroup: (NSMutableSet*)group liberties: (NSMutableSet*)liberties owner: (Player*)owner jens@0: { jens@0: Bit *bit = self.bit; jens@0: if( bit == nil ) { jens@0: if( [liberties containsObject: self] ) jens@0: return; // already traversed jens@0: [liberties addObject: self]; jens@0: } else if( bit.owner==owner ) { jens@0: if( [group containsObject: self] ) jens@0: return; // already traversed jens@0: [group addObject: self]; jens@0: for( GridCell *c in self.neighbors ) jens@0: [c x_addToGroup: group liberties: liberties owner: owner]; jens@0: } jens@0: } jens@0: jens@0: jens@0: - (NSSet*) getGroup: (int*)outLiberties jens@0: { jens@0: NSMutableSet *group=[NSMutableSet set], *liberties=nil; jens@0: if( outLiberties ) jens@0: liberties = [NSMutableSet set]; jens@0: [self x_addToGroup: group liberties: liberties owner: self.bit.owner]; jens@0: if( outLiberties ) jens@0: *outLiberties = liberties.count; jens@0: return group; jens@0: } jens@0: jens@0: jens@0: #pragma mark - jens@0: #pragma mark DRAG-AND-DROP: jens@0: jens@0: jens@1: #if ! TARGET_OS_ASPEN jens@1: jens@0: // An image from another app can be dragged onto a Dispenser to change the Piece's appearance. jens@0: jens@0: jens@0: - (NSDragOperation)draggingEntered:(id )sender jens@0: { jens@0: NSPasteboard *pb = [sender draggingPasteboard]; jens@0: if( [NSImage canInitWithPasteboard: pb] ) jens@0: return NSDragOperationCopy; jens@0: else jens@0: return NSDragOperationNone; jens@0: } jens@0: jens@0: - (BOOL)performDragOperation:(id )sender jens@0: { jens@0: CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard]); jens@0: if( image ) { jens@0: CGColorRef pattern = CreatePatternColor(image); jens@0: _grid.cellColor = pattern; jens@0: CGColorRelease(pattern); jens@0: [_grid setNeedsDisplay]; jens@0: return YES; jens@0: } else jens@0: return NO; jens@0: } jens@0: jens@1: #endif jens@0: jens@0: @end jens@0: jens@0: jens@0: jens@0: jens@0: #pragma mark - jens@0: jens@0: @implementation RectGrid jens@0: jens@0: jens@0: - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns jens@0: spacing: (CGSize)spacing jens@0: position: (CGPoint)pos jens@0: { jens@0: self = [super initWithRows: nRows columns: nColumns spacing: spacing position: pos]; jens@0: if( self ) { jens@0: _cellClass = [Square class]; jens@0: } jens@0: return self; jens@0: } jens@0: jens@0: jens@0: - (CGColorRef) altCellColor {return _altCellColor;} jens@0: - (void) setAltCellColor: (CGColorRef)altCellColor {setcolor(&_altCellColor,altCellColor);} jens@0: jens@0: jens@0: @end jens@0: jens@0: jens@0: jens@0: #pragma mark - jens@0: jens@0: @implementation Square jens@0: jens@0: jens@0: - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill jens@0: { jens@0: if( fill ) { jens@0: CGColorRef c = ((RectGrid*)_grid).altCellColor; jens@0: if( c ) { jens@0: if( ! ((_row+_column) & 1) ) jens@0: c = _grid.cellColor; jens@0: CGContextSetFillColorWithColor(ctx, c); jens@0: } jens@0: } jens@0: [super drawInParentContext: ctx fill: fill]; jens@0: } jens@0: jens@0: jens@0: - (void) setHighlighted: (BOOL)highlighted jens@0: { jens@0: [super setHighlighted: highlighted]; jens@0: self.borderWidth = (highlighted ?6 :0); jens@0: } jens@0: jens@0: jens@0: - (Square*) nw {return (Square*)[_grid cellAtRow: _row+1 column: _column-1];} jens@0: - (Square*) n {return (Square*)[_grid cellAtRow: _row+1 column: _column ];} jens@0: - (Square*) ne {return (Square*)[_grid cellAtRow: _row+1 column: _column+1];} jens@0: - (Square*) e {return (Square*)[_grid cellAtRow: _row column: _column+1];} jens@0: - (Square*) se {return (Square*)[_grid cellAtRow: _row-1 column: _column+1];} jens@0: - (Square*) s {return (Square*)[_grid cellAtRow: _row-1 column: _column ];} jens@0: - (Square*) sw {return (Square*)[_grid cellAtRow: _row-1 column: _column-1];} jens@0: - (Square*) w {return (Square*)[_grid cellAtRow: _row column: _column-1];} jens@0: jens@0: // Directions relative to the current player: jens@0: - (Square*) fl {return self.fwdIsN ?self.nw :self.se;} jens@0: - (Square*) f {return self.fwdIsN ?self.n :self.s;} jens@0: - (Square*) fr {return self.fwdIsN ?self.ne :self.sw;} jens@0: - (Square*) r {return self.fwdIsN ?self.e :self.w;} jens@0: - (Square*) br {return self.fwdIsN ?self.se :self.nw;} jens@0: - (Square*) b {return self.fwdIsN ?self.s :self.n;} jens@0: - (Square*) bl {return self.fwdIsN ?self.sw :self.ne;} jens@0: - (Square*) l {return self.fwdIsN ?self.w :self.e;} jens@0: jens@0: jens@1: #if ! TARGET_OS_ASPEN jens@1: jens@0: - (BOOL)performDragOperation:(id )sender jens@0: { jens@0: CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard]); jens@0: if( image ) { jens@0: CGColorRef color = CreatePatternColor(image); jens@0: RectGrid *rectGrid = (RectGrid*)_grid; jens@0: if( rectGrid.altCellColor && ((_row+_column) & 1) ) jens@0: rectGrid.altCellColor = color; jens@0: else jens@0: rectGrid.cellColor = color; jens@0: CGColorRelease(color); jens@0: [rectGrid setNeedsDisplay]; jens@0: return YES; jens@0: } else jens@0: return NO; jens@0: } jens@0: jens@1: #endif jens@1: jens@0: @end jens@0: jens@0: jens@0: jens@0: #pragma mark - jens@0: jens@0: @implementation GoSquare jens@0: jens@0: @synthesize dotted=_dotted; jens@0: jens@0: - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill jens@0: { jens@0: if( fill ) jens@0: [super drawInParentContext: ctx fill: fill]; jens@0: else { jens@0: CGRect frame = self.frame; jens@0: const CGFloat midx=floor(CGRectGetMidX(frame))+0.5, jens@0: midy=floor(CGRectGetMidY(frame))+0.5; jens@0: CGPoint p[4] = {{CGRectGetMinX(frame),midy}, jens@0: {CGRectGetMaxX(frame),midy}, jens@0: {midx,CGRectGetMinY(frame)}, jens@0: {midx,CGRectGetMaxY(frame)}}; jens@0: if( ! self.s ) p[2].y = midy; jens@0: if( ! self.n ) p[3].y = midy; jens@0: if( ! self.w ) p[0].x = midx; jens@0: if( ! self.e ) p[1].x = midx; jens@0: CGContextStrokeLineSegments(ctx, p, 4); jens@0: jens@0: if( _dotted ) { jens@0: CGContextSetFillColorWithColor(ctx,_grid.lineColor); jens@0: CGRect dot = CGRectMake(midx-2.5, midy-2.5, 5, 5); jens@0: CGContextFillEllipseInRect(ctx, dot); jens@0: } jens@0: } jens@0: } jens@0: jens@1: jens@0: @end