Fixed some memory leaks, and took the address-related properties out of Player.
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,
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 CGRect frame = CGRectMake(col*_spacing.width, row*_spacing.height,
154 _spacing.width,_spacing.height);
155 cell = [self createCellAtRow: row column: col suggestedFrame: frame];
157 [_cells replaceObjectAtIndex: index withObject: cell];
158 [self addSublayer: cell];
159 [self setNeedsDisplay];
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];
174 - (void) removeCellAtRow: (unsigned)row column: (unsigned)col
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];
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];
197 - (GridCell*) cellWithName: (NSString*)name
199 for( CALayer *layer in self.sublayers )
200 if( [layer isKindOfClass: [GridCell class]] )
201 if( [name isEqualToString: ((GridCell*)layer).name] )
202 return (GridCell*)layer;
207 - (NSCountedSet*) countPiecesByPlayer
209 NSCountedSet *players = [NSCountedSet set];
210 for( GridCell *cell in self.cells ) {
211 Player *owner = cell.bit.owner;
213 [players addObject: owner];
221 #pragma mark GAME STATE:
224 - (NSString*) stateString
226 NSMutableString *state = [NSMutableString stringWithCapacity: _cells.count];
227 for( GridCell *cell in self.cells ) {
229 NSString *name = bit ?bit.name :@"-";
230 NSAssert(name.length==1,@"Missing or multicharacter name");
231 [state appendString: name];
236 - (void) setStateString: (NSString*)state
238 Game *game = self.game;
240 for( GridCell *cell in self.cells )
241 cell.bit = [game makePieceNamed: [state substringWithRange: NSMakeRange(i++,1)]];
245 - (BOOL) applyMoveString: (NSString*)move
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];
254 if( src && ! [self.game animateMoveFrom: src to: dst] )
263 #pragma mark DRAWING:
266 - (void) drawCellsInContext: (CGContextRef)ctx fill: (BOOL)fill
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];
273 [cell drawInParentContext: ctx fill: fill];
278 - (void)drawInContext:(CGContextRef)ctx
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];
284 if( _backgroundImage )
285 CGContextDrawImage(ctx, self.bounds, _backgroundImage);
288 CGContextSetFillColorWithColor(ctx, _cellColor);
289 [self drawCellsInContext: ctx fill: YES];
292 CGContextSetStrokeColorWithColor(ctx,_lineColor);
293 [self drawCellsInContext:ctx fill: NO];
304 @implementation GridCell
307 - (id) initWithGrid: (Grid*)grid
308 row: (unsigned)row column: (unsigned)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:)
327 - (NSString*) description
329 return [NSString stringWithFormat: @"%@(%u,%u)", [self class],_column,_row];
332 @synthesize grid=_grid, row=_row, column=_column;
335 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
337 // Default implementation just fills or outlines the cell.
338 CGRect frame = self.frame;
340 CGContextFillRect(ctx,frame);
342 CGContextStrokeRect(ctx, frame);
346 - (void) setBit: (Bit*)bit
348 if( bit != self.bit ) {
352 CGSize size = self.bounds.size;
353 bit.position = CGPointMake(floor(size.width/2.0),
354 floor(size.height/2.0));
359 - (Bit*) canDragBit: (Bit*)bit
361 if( _grid.allowsMoves && bit==self.bit )
362 return [super canDragBit: bit];
367 - (BOOL) canDropBit: (Bit*)bit atPoint: (CGPoint)point
369 return self.bit == nil || _grid.allowsCaptures;
375 return self.game.currentPlayer.index == 0;
379 - (NSArray*) neighbors
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];
388 [neighbors addObject: cell];
394 // Recursive subroutine used by getGroup:.
395 - (void) x_addToGroup: (NSMutableSet*)group liberties: (NSMutableSet*)liberties owner: (Player*)owner
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];
412 - (NSSet*) getGroup: (int*)outLiberties
414 NSMutableSet *group=[NSMutableSet set], *liberties=nil;
416 liberties = [NSMutableSet set];
417 [self x_addToGroup: group liberties: liberties owner: self.bit.owner];
419 *outLiberties = liberties.count;
425 #pragma mark DRAG-AND-DROP:
428 #if ! TARGET_OS_IPHONE
430 // An image from another app can be dragged onto a Grid to change its background pattern.
432 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
434 if( CanGetCGImageFromPasteboard([sender draggingPasteboard]) )
435 return NSDragOperationCopy;
437 return NSDragOperationNone;
440 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
442 CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard],sender);
444 CGColorRef pattern = CreatePatternColor(image);
445 _grid.cellColor = pattern;
446 CGColorRelease(pattern);
447 [_grid setNeedsDisplay];
462 @implementation RectGrid
465 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
466 spacing: (CGSize)spacing
467 position: (CGPoint)pos
469 self = [super initWithRows: nRows columns: nColumns spacing: spacing position: pos];
471 _cellClass = [Square class];
477 - (CGColorRef) altCellColor {return _altCellColor;}
478 - (void) setAltCellColor: (CGColorRef)altCellColor {setcolor(&_altCellColor,altCellColor);}
487 @implementation Square
490 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
493 CGColorRef c = ((RectGrid*)_grid).altCellColor;
495 if( ! ((_row+_column) & 1) )
497 CGContextSetFillColorWithColor(ctx, c);
500 [super drawInParentContext: ctx fill: fill];
504 - (void) setHighlighted: (BOOL)highlighted
506 [super setHighlighted: highlighted];
507 self.cornerRadius = self.bounds.size.width/2.0;
508 self.borderWidth = (highlighted ?6 :0);
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];}
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;}
532 static int sgn( int n ) {return n<0 ?-1 :(n>0 ?1 :0);}
535 - (SEL) directionToCell: (GridCell*)dst
537 static NSString* const kDirections[9] = {@"sw", @"s", @"se",
540 if( dst.grid != self.grid )
542 int dy=dst.row-_row, dx=dst.column-_column;
544 if( !( _grid.usesDiagonals && abs(dx)==abs(dy) ) )
546 NSString *dir = kDirections[ 3*(sgn(dy)+1) + (sgn(dx)+1) ];
547 return dir ?NSSelectorFromString(dir) :NULL;
550 - (NSArray*) lineToCell: (GridCell*)dst inclusive: (BOOL)inclusive;
552 SEL dir = [self directionToCell: dst];
555 NSMutableArray *line = [NSMutableArray array];
557 for( cell=self; cell; cell = [cell performSelector: dir] ) {
558 if( inclusive || (cell!=self && cell!=dst) )
559 [line addObject: cell];
563 return nil; // should be impossible, but just in case
567 #if ! TARGET_OS_IPHONE
569 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
571 CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard],sender);
573 CGColorRef color = CreatePatternColor(image);
574 RectGrid *rectGrid = (RectGrid*)_grid;
575 if( rectGrid.altCellColor && ((_row+_column) & 1) )
576 rectGrid.altCellColor = color;
578 rectGrid.cellColor = color;
579 CGColorRelease(color);
580 [rectGrid setNeedsDisplay];
594 @implementation GoSquare
596 @synthesize dotted=_dotted;
598 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
601 [super drawInParentContext: ctx fill: fill];
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);
617 CGContextSetFillColorWithColor(ctx,_grid.lineColor);
618 CGRect dot = CGRectMake(midx-2.5, midy-2.5, 5, 5);
619 CGContextFillEllipseInRect(ctx, dot);