Updated DemoBoardView and the xcode project to work with the latest sources.
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.
5 Redistribution and use in source and binary forms, with or without modification, are permitted
6 provided that the following conditions are met:
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.
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.
26 #import "Game+Protected.h"
28 #import "QuartzUtils.h"
34 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
35 spacing: (CGSize)spacing
36 position: (CGPoint)pos
38 NSParameterAssert(nRows>0 && nColumns>0);
44 _cellClass = [GridCell class];
45 self.lineColor = kBlackColor;
49 self.bounds = CGRectMake(-1, -1, nColumns*spacing.width+2, nRows*spacing.height+2);
51 self.anchorPoint = CGPointMake(0,0);
52 self.zPosition = kBoardZ;
53 self.needsDisplayOnBoundsChange = YES;
55 unsigned n = nRows*nColumns;
56 _cells = [[NSMutableArray alloc] initWithCapacity: n];
57 id null = [NSNull null];
59 [_cells addObject: null];
61 [self setNeedsDisplay];
67 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
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 );
77 return [self initWithRows: nRows columns: nColumns
78 spacing: CGSizeMake(spacing,spacing)
85 CGColorRelease(_cellColor);
86 CGColorRelease(_lineColor);
92 static void setcolor( CGColorRef *var, CGColorRef color )
96 *var = CGColorRetain(color);
100 - (CGColorRef) cellColor {return _cellColor;}
101 - (void) setCellColor: (CGColorRef)cellColor {setcolor(&_cellColor,cellColor);}
103 - (CGColorRef) lineColor {return _lineColor;}
104 - (void) setLineColor: (CGColorRef)lineColor {setcolor(&_lineColor,lineColor);}
106 - (CGImageRef) backgroundImage {return _backgroundImage;}
107 - (void) setBackgroundImage: (CGImageRef)image
109 if( image != _backgroundImage ) {
110 CGImageRelease(_backgroundImage);
111 _backgroundImage = CGImageRetain(image);
115 @synthesize cellClass=_cellClass, rows=_nRows, columns=_nColumns, spacing=_spacing, reversed=_reversed,
116 usesDiagonals=_usesDiagonals, allowsMoves=_allowsMoves, allowsCaptures=_allowsCaptures;
120 #pragma mark GEOMETRY:
123 - (GridCell*) cellAtRow: (unsigned)row column: (unsigned)col
125 if( row < _nRows && col < _nColumns ) {
126 id cell = [_cells objectAtIndex: row*_nColumns+col];
127 if( cell != [NSNull null] )
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
138 GridCell *cell = [[_cellClass alloc] initWithGrid: self
141 cell.name = [NSString stringWithFormat: @"%c%u", ('A'+row),(1+col)];
142 return [cell autorelease];
146 - (GridCell*) addCellAtRow: (unsigned)row column: (unsigned)col
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 unsigned effectiveRow=row, effectiveCol=col;
155 effectiveRow = _nRows-1 - effectiveRow;
156 effectiveCol = _nColumns-1 - effectiveCol;
158 CGRect frame = CGRectMake(effectiveCol*_spacing.width, effectiveRow*_spacing.height,
159 _spacing.width,_spacing.height);
160 cell = [self createCellAtRow: row column: col suggestedFrame: frame];
162 [_cells replaceObjectAtIndex: index withObject: cell];
163 [self addSublayer: cell];
164 [self setNeedsDisplay];
173 for( int row=_nRows-1; row>=0; row-- ) // makes 'upper' cells be in 'back'
174 for( int col=0; col<_nColumns; col++ )
175 [self addCellAtRow: row column: col];
179 - (void) removeCellAtRow: (unsigned)row column: (unsigned)col
181 NSParameterAssert(row<_nRows);
182 NSParameterAssert(col<_nColumns);
183 unsigned index = row*_nColumns+col;
184 id cell = [_cells objectAtIndex: index];
185 if( cell != [NSNull null] )
186 [cell removeFromSuperlayer];
187 [_cells replaceObjectAtIndex: index withObject: [NSNull null]];
188 [self setNeedsDisplay];
194 NSMutableArray *cells = [_cells mutableCopy];
195 for( int i=cells.count-1; i>=0; i-- )
196 if( [cells objectAtIndex: i] == [NSNull null] )
197 [cells removeObjectAtIndex: i];
202 - (GridCell*) cellWithName: (NSString*)name
204 for( CALayer *layer in self.sublayers )
205 if( [layer isKindOfClass: [GridCell class]] )
206 if( [name isEqualToString: ((GridCell*)layer).name] )
207 return (GridCell*)layer;
212 - (NSCountedSet*) countPiecesByPlayer
214 NSCountedSet *players = [NSCountedSet set];
215 for( GridCell *cell in self.cells ) {
216 Player *owner = cell.bit.owner;
218 [players addObject: owner];
226 #pragma mark GAME STATE:
229 - (NSString*) stateString
231 NSMutableString *state = [NSMutableString stringWithCapacity: _cells.count];
232 for( GridCell *cell in self.cells ) {
234 NSString *name = bit ?bit.name :@"-";
235 NSAssert(name.length==1,@"Missing or multicharacter name");
236 [state appendString: name];
241 - (void) setStateString: (NSString*)state
243 Game *game = self.game;
245 for( GridCell *cell in self.cells )
246 cell.bit = [game makePieceNamed: [state substringWithRange: NSMakeRange(i++,1)]];
250 - (BOOL) applyMoveString: (NSString*)move
253 for( NSString *ident in [move componentsSeparatedByString: @"-"] ) {
254 while( [ident hasSuffix: @"!"] || [ident hasSuffix: @"*"] )
255 ident = [ident substringToIndex: ident.length-1];
256 GridCell *dst = [self cellWithName: ident];
259 if( src && ! [self.game animateMoveFrom: src to: dst] )
268 #pragma mark DRAWING:
271 - (void) drawCellsInContext: (CGContextRef)ctx fill: (BOOL)fill
273 // Subroutine of -drawInContext:. Draws all the cells, with or without a fill.
274 for( unsigned row=0; row<_nRows; row++ )
275 for( unsigned col=0; col<_nColumns; col++ ) {
276 GridCell *cell = [self cellAtRow: row column: col];
278 [cell drawInParentContext: ctx fill: fill];
282 - (void) drawBackgroundInContext: (CGContextRef)ctx
284 if( _backgroundImage ) {
285 CGRect bounds = self.bounds;
287 CGContextSaveGState(ctx);
288 CGContextRotateCTM(ctx, M_PI);
289 CGContextTranslateCTM(ctx, -bounds.size.width, -bounds.size.height);
291 CGContextDrawImage(ctx, bounds, _backgroundImage);
293 CGContextRestoreGState(ctx);
298 - (void)drawInContext:(CGContextRef)ctx
300 // Custom CALayer drawing implementation. Delegates to the cells to draw themselves
301 // in me; this is more efficient than having each cell have its own drawing.
302 [super drawInContext: ctx];
304 [self drawBackgroundInContext: ctx];
307 CGContextSetFillColorWithColor(ctx, _cellColor);
308 [self drawCellsInContext: ctx fill: YES];
311 CGContextSetStrokeColorWithColor(ctx,_lineColor);
312 [self drawCellsInContext:ctx fill: NO];
323 @implementation GridCell
326 - (id) initWithGrid: (Grid*)grid
327 row: (unsigned)row column: (unsigned)col
335 self.position = frame.origin;
336 CGRect bounds = frame;
337 bounds.origin.x -= floor(bounds.origin.x); // make sure my coords fall on pixel boundaries
338 bounds.origin.y -= floor(bounds.origin.y);
339 self.bounds = bounds;
340 self.anchorPoint = CGPointMake(0,0);
341 self.borderColor = kHighlightColor; // Used when highlighting (see -setHighlighted:)
346 - (NSString*) description
348 return [NSString stringWithFormat: @"%@(%u,%u)", [self class],_column,_row];
351 @synthesize grid=_grid, row=_row, column=_column;
354 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
356 // Default implementation just fills or outlines the cell.
357 CGRect frame = self.frame;
359 CGContextFillRect(ctx,frame);
361 CGContextStrokeRect(ctx, frame);
365 - (void) setBit: (Bit*)bit
367 if( bit != self.bit ) {
371 CGSize size = self.bounds.size;
372 bit.position = CGPointMake(floor(size.width/2.0),
373 floor(size.height/2.0));
378 - (Bit*) canDragBit: (Bit*)bit
380 if( _grid.allowsMoves && bit==self.bit )
381 return [super canDragBit: bit];
386 - (BOOL) canDropBit: (Bit*)bit atPoint: (CGPoint)point
388 return self.bit == nil || _grid.allowsCaptures;
394 return self.game.currentPlayer.index == 0;
398 - (NSArray*) neighbors
400 BOOL orthogonal = ! _grid.usesDiagonals;
401 NSMutableArray *neighbors = [NSMutableArray arrayWithCapacity: 8];
402 for( int dy=-1; dy<=1; dy++ )
403 for( int dx=-1; dx<=1; dx++ )
404 if( (dx || dy) && !(orthogonal && dx && dy) ) {
405 GridCell *cell = [_grid cellAtRow: _row+dy column: _column+dx];
407 [neighbors addObject: cell];
413 // Recursive subroutine used by getGroup:.
414 - (void) x_addToGroup: (NSMutableSet*)group liberties: (NSMutableSet*)liberties owner: (Player*)owner
418 if( [liberties containsObject: self] )
419 return; // already traversed
420 [liberties addObject: self];
421 } else if( bit.owner==owner ) {
422 if( [group containsObject: self] )
423 return; // already traversed
424 [group addObject: self];
425 for( GridCell *c in self.neighbors )
426 [c x_addToGroup: group liberties: liberties owner: owner];
431 - (NSSet*) getGroup: (int*)outLiberties
433 NSMutableSet *group=[NSMutableSet set], *liberties=nil;
435 liberties = [NSMutableSet set];
436 [self x_addToGroup: group liberties: liberties owner: self.bit.owner];
438 *outLiberties = liberties.count;
444 #pragma mark DRAG-AND-DROP:
447 #if ! TARGET_OS_IPHONE
449 // An image from another app can be dragged onto a Grid to change its background pattern.
451 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
453 if( CanGetCGImageFromPasteboard([sender draggingPasteboard]) )
454 return NSDragOperationCopy;
456 return NSDragOperationNone;
459 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
461 CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard],sender);
463 CGColorRef pattern = CreatePatternColor(image);
464 _grid.cellColor = pattern;
465 CGColorRelease(pattern);
466 [_grid setNeedsDisplay];
481 @implementation RectGrid
484 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
485 spacing: (CGSize)spacing
486 position: (CGPoint)pos
488 self = [super initWithRows: nRows columns: nColumns spacing: spacing position: pos];
490 _cellClass = [Square class];
496 - (CGColorRef) altCellColor {return _altCellColor;}
497 - (void) setAltCellColor: (CGColorRef)altCellColor {setcolor(&_altCellColor,altCellColor);}
506 @implementation Square
509 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
512 CGColorRef c = ((RectGrid*)_grid).altCellColor;
514 if( ! ((_row+_column) & 1) )
516 CGContextSetFillColorWithColor(ctx, c);
519 [super drawInParentContext: ctx fill: fill];
523 - (void) setHighlighted: (BOOL)highlighted
525 [super setHighlighted: highlighted];
526 self.cornerRadius = self.bounds.size.width/2.0;
527 self.borderWidth = (highlighted ?6 :0);
531 - (Square*) nw {return (Square*)[_grid cellAtRow: _row+1 column: _column-1];}
532 - (Square*) n {return (Square*)[_grid cellAtRow: _row+1 column: _column ];}
533 - (Square*) ne {return (Square*)[_grid cellAtRow: _row+1 column: _column+1];}
534 - (Square*) e {return (Square*)[_grid cellAtRow: _row column: _column+1];}
535 - (Square*) se {return (Square*)[_grid cellAtRow: _row-1 column: _column+1];}
536 - (Square*) s {return (Square*)[_grid cellAtRow: _row-1 column: _column ];}
537 - (Square*) sw {return (Square*)[_grid cellAtRow: _row-1 column: _column-1];}
538 - (Square*) w {return (Square*)[_grid cellAtRow: _row column: _column-1];}
540 // Directions relative to the current player:
541 - (Square*) fl {return self.fwdIsN ?self.nw :self.se;}
542 - (Square*) f {return self.fwdIsN ?self.n :self.s;}
543 - (Square*) fr {return self.fwdIsN ?self.ne :self.sw;}
544 - (Square*) r {return self.fwdIsN ?self.e :self.w;}
545 - (Square*) br {return self.fwdIsN ?self.se :self.nw;}
546 - (Square*) b {return self.fwdIsN ?self.s :self.n;}
547 - (Square*) bl {return self.fwdIsN ?self.sw :self.ne;}
548 - (Square*) l {return self.fwdIsN ?self.w :self.e;}
551 static int sgn( int n ) {return n<0 ?-1 :(n>0 ?1 :0);}
554 - (SEL) directionToCell: (GridCell*)dst
556 static NSString* const kDirections[9] = {@"sw", @"s", @"se",
559 if( dst.grid != self.grid )
561 int dy=dst.row-_row, dx=dst.column-_column;
563 if( !( _grid.usesDiagonals && abs(dx)==abs(dy) ) )
565 NSString *dir = kDirections[ 3*(sgn(dy)+1) + (sgn(dx)+1) ];
566 return dir ?NSSelectorFromString(dir) :NULL;
569 - (NSArray*) lineToCell: (GridCell*)dst inclusive: (BOOL)inclusive;
571 SEL dir = [self directionToCell: dst];
574 NSMutableArray *line = [NSMutableArray array];
576 for( cell=self; cell; cell = [cell performSelector: dir] ) {
577 if( inclusive || (cell!=self && cell!=dst) )
578 [line addObject: cell];
582 return nil; // should be impossible, but just in case
586 #if ! TARGET_OS_IPHONE
588 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
590 CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard],sender);
592 CGColorRef color = CreatePatternColor(image);
593 RectGrid *rectGrid = (RectGrid*)_grid;
594 if( rectGrid.altCellColor && ((_row+_column) & 1) )
595 rectGrid.altCellColor = color;
597 rectGrid.cellColor = color;
598 CGColorRelease(color);
599 [rectGrid setNeedsDisplay];
613 @implementation GoSquare
615 @synthesize dotted=_dotted;
617 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
620 [super drawInParentContext: ctx fill: fill];
622 CGRect frame = self.frame;
623 const CGFloat midx=floor(CGRectGetMidX(frame))+0.5,
624 midy=floor(CGRectGetMidY(frame))+0.5;
625 CGPoint p[4] = {{CGRectGetMinX(frame),midy},
626 {CGRectGetMaxX(frame),midy},
627 {midx,CGRectGetMinY(frame)},
628 {midx,CGRectGetMaxY(frame)}};
629 if( ! self.s ) p[2].y = midy;
630 if( ! self.n ) p[3].y = midy;
631 if( ! self.w ) p[0].x = midx;
632 if( ! self.e ) p[1].x = midx;
633 CGContextStrokeLineSegments(ctx, p, 4);
636 CGContextSetFillColorWithColor(ctx,_grid.lineColor);
637 CGRect dot = CGRectMake(midx-2.5, midy-2.5, 5, 5);
638 CGContextFillEllipseInRect(ctx, dot);