* Fixed scaling of king pieces when the board's state is set.
* Added IBOutlets for tilting the BoardView.
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"
31 @interface GridCell ()
32 - (void) setBitTransform: (CATransform3D)bitTransform;
39 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
40 spacing: (CGSize)spacing
41 position: (CGPoint)pos
43 NSParameterAssert(nRows>0 && nColumns>0);
49 _cellClass = [GridCell class];
50 self.lineColor = kBlackColor;
53 _bitTransform = CATransform3DIdentity;
55 self.bounds = CGRectMake(-1, -1, nColumns*spacing.width+2, nRows*spacing.height+2);
57 self.anchorPoint = CGPointMake(0,0);
58 self.zPosition = kBoardZ;
59 self.needsDisplayOnBoundsChange = YES;
61 unsigned n = nRows*nColumns;
62 _cells = [[NSMutableArray alloc] initWithCapacity: n];
63 id null = [NSNull null];
65 [_cells addObject: null];
67 [self setNeedsDisplay];
73 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
76 CGFloat spacing = floor(MIN( (frame.size.width -2)/(CGFloat)nColumns,
77 (frame.size.height-2)/(CGFloat)nRows) );
78 CGSize size = CGSizeMake(nColumns*spacing+2, nRows*spacing+2);
79 CGPoint position = frame.origin;
80 position.x += round( (frame.size.width -size.width )/2.0 );
81 position.y += round( (frame.size.height-size.height)/2.0 );
83 return [self initWithRows: nRows columns: nColumns
84 spacing: CGSizeMake(spacing,spacing)
91 CGColorRelease(_cellColor);
92 CGColorRelease(_lineColor);
98 static void setcolor( CGColorRef *var, CGColorRef color )
100 if( color != *var ) {
101 CGColorRelease(*var);
102 *var = CGColorRetain(color);
106 - (CGColorRef) cellColor {return _cellColor;}
107 - (void) setCellColor: (CGColorRef)cellColor {setcolor(&_cellColor,cellColor);}
109 - (CGColorRef) lineColor {return _lineColor;}
110 - (void) setLineColor: (CGColorRef)lineColor {setcolor(&_lineColor,lineColor);}
112 - (CGImageRef) backgroundImage {return _backgroundImage;}
113 - (void) setBackgroundImage: (CGImageRef)image
115 if( image != _backgroundImage ) {
116 CGImageRelease(_backgroundImage);
117 _backgroundImage = CGImageRetain(image);
121 @synthesize cellClass=_cellClass, rows=_nRows, columns=_nColumns, spacing=_spacing, reversed=_reversed,
122 usesDiagonals=_usesDiagonals, allowsMoves=_allowsMoves, allowsCaptures=_allowsCaptures,
123 bitTransform=_bitTransform;
127 #pragma mark GEOMETRY:
130 - (GridCell*) cellAtRow: (unsigned)row column: (unsigned)col
132 if( row < _nRows && col < _nColumns ) {
133 id cell = [_cells objectAtIndex: row*_nColumns+col];
134 if( cell != [NSNull null] )
141 /** Subclasses can override this, to change the cell's class or frame. */
142 - (GridCell*) createCellAtRow: (unsigned)row column: (unsigned)col
143 suggestedFrame: (CGRect)frame
145 GridCell *cell = [[_cellClass alloc] initWithGrid: self
148 cell.name = [NSString stringWithFormat: @"%c%u", ('A'+row),(1+col)];
149 return [cell autorelease];
153 - (GridCell*) addCellAtRow: (unsigned)row column: (unsigned)col
155 NSParameterAssert(row<_nRows);
156 NSParameterAssert(col<_nColumns);
157 unsigned index = row*_nColumns+col;
158 GridCell *cell = [_cells objectAtIndex: index];
159 if( (id)cell == [NSNull null] ) {
160 unsigned effectiveRow=row, effectiveCol=col;
162 effectiveRow = _nRows-1 - effectiveRow;
163 effectiveCol = _nColumns-1 - effectiveCol;
165 CGRect frame = CGRectMake(effectiveCol*_spacing.width, effectiveRow*_spacing.height,
166 _spacing.width,_spacing.height);
167 cell = [self createCellAtRow: row column: col suggestedFrame: frame];
169 [_cells replaceObjectAtIndex: index withObject: cell];
170 //[self addSublayer: cell];
171 [self insertSublayer: cell atIndex: 0];
172 [self setNeedsDisplay];
181 for( int row=_nRows-1; row>=0; row-- ) // makes 'upper' cells be in 'back'
182 for( int col=0; col<_nColumns; col++ )
183 [self addCellAtRow: row column: col];
187 - (void) removeCellAtRow: (unsigned)row column: (unsigned)col
189 NSParameterAssert(row<_nRows);
190 NSParameterAssert(col<_nColumns);
191 unsigned index = row*_nColumns+col;
192 id cell = [_cells objectAtIndex: index];
193 if( cell != [NSNull null] )
194 [cell removeFromSuperlayer];
195 [_cells replaceObjectAtIndex: index withObject: [NSNull null]];
196 [self setNeedsDisplay];
202 NSMutableArray *cells = [_cells mutableCopy];
203 for( int i=cells.count-1; i>=0; i-- )
204 if( [cells objectAtIndex: i] == [NSNull null] )
205 [cells removeObjectAtIndex: i];
210 - (GridCell*) cellWithName: (NSString*)name
212 for( CALayer *layer in self.sublayers )
213 if( [layer isKindOfClass: [GridCell class]] )
214 if( [name isEqualToString: ((GridCell*)layer).name] )
215 return (GridCell*)layer;
220 - (NSCountedSet*) countPiecesByPlayer
222 NSCountedSet *players = [NSCountedSet set];
223 for( GridCell *cell in self.cells ) {
224 Player *owner = cell.bit.owner;
226 [players addObject: owner];
232 - (CATransform3D) bitTransform
234 return _bitTransform;
237 - (void) setBitTransform: (CATransform3D)t
240 for( GridCell *cell in self.cells )
241 [cell setBitTransform: t];
244 - (void) updateCellTransform
246 CATransform3D t = self.aggregateTransform;
247 t.m41 = t.m42 = t.m43 = 0.0f; // remove translation component
248 t = CATransform3DInvert(t);
249 self.bitTransform = t;
254 #pragma mark GAME STATE:
257 - (NSString*) stateString
259 NSMutableString *state = [NSMutableString stringWithCapacity: _cells.count];
260 for( GridCell *cell in self.cells ) {
262 NSString *name = bit ?bit.name :@"-";
263 NSAssert(name.length==1,@"Missing or multicharacter name");
264 [state appendString: name];
269 - (void) setStateString: (NSString*)state
271 Game *game = self.game;
273 for( GridCell *cell in self.cells )
274 cell.bit = [game makePieceNamed: [state substringWithRange: NSMakeRange(i++,1)]];
278 - (BOOL) applyMoveString: (NSString*)move
281 for( NSString *ident in [move componentsSeparatedByString: @"-"] ) {
282 while( [ident hasSuffix: @"!"] || [ident hasSuffix: @"*"] )
283 ident = [ident substringToIndex: ident.length-1];
284 GridCell *dst = [self cellWithName: ident];
287 if( src && ! [self.game animateMoveFrom: src to: dst] )
296 #pragma mark DRAWING:
299 - (void) drawCellsInContext: (CGContextRef)ctx fill: (BOOL)fill
301 // Subroutine of -drawInContext:. Draws all the cells, with or without a fill.
302 for( unsigned row=0; row<_nRows; row++ )
303 for( unsigned col=0; col<_nColumns; col++ ) {
304 GridCell *cell = [self cellAtRow: row column: col];
306 [cell drawInParentContext: ctx fill: fill];
310 - (void) drawBackgroundInContext: (CGContextRef)ctx
312 if( _backgroundImage ) {
313 CGRect bounds = self.bounds;
315 CGContextSaveGState(ctx);
316 CGContextRotateCTM(ctx, M_PI);
317 CGContextTranslateCTM(ctx, -bounds.size.width, -bounds.size.height);
319 CGContextDrawImage(ctx, bounds, _backgroundImage);
321 CGContextRestoreGState(ctx);
326 - (void)drawInContext:(CGContextRef)ctx
328 // Custom CALayer drawing implementation. Delegates to the cells to draw themselves
329 // in me; this is more efficient than having each cell have its own drawing.
330 [super drawInContext: ctx];
332 [self drawBackgroundInContext: ctx];
335 CGContextSetFillColorWithColor(ctx, _cellColor);
336 [self drawCellsInContext: ctx fill: YES];
339 CGContextSetStrokeColorWithColor(ctx,_lineColor);
340 [self drawCellsInContext:ctx fill: NO];
351 @implementation GridCell
354 - (id) initWithGrid: (Grid*)grid
355 row: (unsigned)row column: (unsigned)col
363 self.anchorPoint = CGPointMake(0,0);
364 self.position = frame.origin;
365 CGRect bounds = frame;
366 bounds.origin.x -= floor(bounds.origin.x); // make sure my coords fall on pixel boundaries
367 bounds.origin.y -= floor(bounds.origin.y);
368 self.bounds = bounds;
369 self.borderColor = kHighlightColor; // Used when highlighting (see -setHighlighted:)
370 [self setBitTransform: grid.bitTransform];
375 - (NSString*) description
377 return [NSString stringWithFormat: @"%@(%u,%u)", [self class],_column,_row];
380 @synthesize grid=_grid, row=_row, column=_column;
383 - (void) setBitTransform: (CATransform3D)bitTransform
385 // To make the bitTransform relative to my center, I need to offset the center to the origin
386 // first, and then back afterwards.
387 CGSize size = self.bounds.size;
388 CATransform3D x = CATransform3DMakeTranslation(-size.width/2, -size.height/2,0);
389 x = CATransform3DConcat(x, bitTransform);
390 x = CATransform3DConcat(x, CATransform3DMakeTranslation(size.width/2, size.height/2,0));
391 self.sublayerTransform = x;
395 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
397 // Default implementation just fills or outlines the cell.
398 CGRect frame = self.frame;
400 CGContextFillRect(ctx,frame);
402 CGContextStrokeRect(ctx, frame);
406 - (void) setBit: (Bit*)bit
408 if( bit != self.bit ) {
411 bit.position = GetCGRectCenter(self.bounds);
415 - (Bit*) canDragBit: (Bit*)bit
417 if( _grid.allowsMoves && bit==self.bit )
418 return [super canDragBit: bit];
423 - (BOOL) canDropBit: (Bit*)bit atPoint: (CGPoint)point
425 return self.bit == nil || _grid.allowsCaptures;
431 return self.game.currentPlayer.index == 0;
435 - (NSArray*) neighbors
437 BOOL orthogonal = ! _grid.usesDiagonals;
438 NSMutableArray *neighbors = [NSMutableArray arrayWithCapacity: 8];
439 for( int dy=-1; dy<=1; dy++ )
440 for( int dx=-1; dx<=1; dx++ )
441 if( (dx || dy) && !(orthogonal && dx && dy) ) {
442 GridCell *cell = [_grid cellAtRow: _row+dy column: _column+dx];
444 [neighbors addObject: cell];
450 // Recursive subroutine used by getGroup:.
451 - (void) x_addToGroup: (NSMutableSet*)group liberties: (NSMutableSet*)liberties owner: (Player*)owner
455 if( [liberties containsObject: self] )
456 return; // already traversed
457 [liberties addObject: self];
458 } else if( bit.owner==owner ) {
459 if( [group containsObject: self] )
460 return; // already traversed
461 [group addObject: self];
462 for( GridCell *c in self.neighbors )
463 [c x_addToGroup: group liberties: liberties owner: owner];
468 - (NSSet*) getGroup: (int*)outLiberties
470 NSMutableSet *group=[NSMutableSet set], *liberties=nil;
472 liberties = [NSMutableSet set];
473 [self x_addToGroup: group liberties: liberties owner: self.bit.owner];
475 *outLiberties = liberties.count;
481 #pragma mark DRAG-AND-DROP:
484 #if ! TARGET_OS_IPHONE
486 // An image from another app can be dragged onto a Grid to change its background pattern.
488 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
490 if( CanGetCGImageFromPasteboard([sender draggingPasteboard]) )
491 return NSDragOperationCopy;
493 return NSDragOperationNone;
496 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
498 CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard],sender);
500 CGColorRef pattern = CreatePatternColor(image);
501 _grid.cellColor = pattern;
502 CGColorRelease(pattern);
503 [_grid setNeedsDisplay];
518 @implementation RectGrid
521 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
522 spacing: (CGSize)spacing
523 position: (CGPoint)pos
525 self = [super initWithRows: nRows columns: nColumns spacing: spacing position: pos];
527 _cellClass = [Square class];
533 - (CGColorRef) altCellColor {return _altCellColor;}
534 - (void) setAltCellColor: (CGColorRef)altCellColor {setcolor(&_altCellColor,altCellColor);}
543 @implementation Square
546 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
549 CGColorRef c = ((RectGrid*)_grid).altCellColor;
551 if( ! ((_row+_column) & 1) )
553 CGContextSetFillColorWithColor(ctx, c);
556 [super drawInParentContext: ctx fill: fill];
560 - (void) setHighlighted: (BOOL)highlighted
562 [super setHighlighted: highlighted];
563 self.cornerRadius = self.bounds.size.width/2.0;
564 self.borderWidth = (highlighted ?6 :0);
568 - (Square*) nw {return (Square*)[_grid cellAtRow: _row+1 column: _column-1];}
569 - (Square*) n {return (Square*)[_grid cellAtRow: _row+1 column: _column ];}
570 - (Square*) ne {return (Square*)[_grid cellAtRow: _row+1 column: _column+1];}
571 - (Square*) e {return (Square*)[_grid cellAtRow: _row column: _column+1];}
572 - (Square*) se {return (Square*)[_grid cellAtRow: _row-1 column: _column+1];}
573 - (Square*) s {return (Square*)[_grid cellAtRow: _row-1 column: _column ];}
574 - (Square*) sw {return (Square*)[_grid cellAtRow: _row-1 column: _column-1];}
575 - (Square*) w {return (Square*)[_grid cellAtRow: _row column: _column-1];}
577 // Directions relative to the current player:
578 - (Square*) fl {return self.fwdIsN ?self.nw :self.se;}
579 - (Square*) f {return self.fwdIsN ?self.n :self.s;}
580 - (Square*) fr {return self.fwdIsN ?self.ne :self.sw;}
581 - (Square*) r {return self.fwdIsN ?self.e :self.w;}
582 - (Square*) br {return self.fwdIsN ?self.se :self.nw;}
583 - (Square*) b {return self.fwdIsN ?self.s :self.n;}
584 - (Square*) bl {return self.fwdIsN ?self.sw :self.ne;}
585 - (Square*) l {return self.fwdIsN ?self.w :self.e;}
588 static int sgn( int n ) {return n<0 ?-1 :(n>0 ?1 :0);}
591 - (SEL) directionToCell: (GridCell*)dst
593 static NSString* const kDirections[9] = {@"sw", @"s", @"se",
596 if( dst.grid != self.grid )
598 int dy=dst.row-_row, dx=dst.column-_column;
600 if( !( _grid.usesDiagonals && abs(dx)==abs(dy) ) )
602 NSString *dir = kDirections[ 3*(sgn(dy)+1) + (sgn(dx)+1) ];
603 return dir ?NSSelectorFromString(dir) :NULL;
606 - (NSArray*) lineToCell: (GridCell*)dst inclusive: (BOOL)inclusive;
608 SEL dir = [self directionToCell: dst];
611 NSMutableArray *line = [NSMutableArray array];
613 for( cell=self; cell; cell = [cell performSelector: dir] ) {
614 if( inclusive || (cell!=self && cell!=dst) )
615 [line addObject: cell];
619 return nil; // should be impossible, but just in case
623 #if ! TARGET_OS_IPHONE
625 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
627 CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard],sender);
629 CGColorRef color = CreatePatternColor(image);
630 RectGrid *rectGrid = (RectGrid*)_grid;
631 if( rectGrid.altCellColor && ((_row+_column) & 1) )
632 rectGrid.altCellColor = color;
634 rectGrid.cellColor = color;
635 CGColorRelease(color);
636 [rectGrid setNeedsDisplay];
650 @implementation GoSquare
652 @synthesize dotted=_dotted;
654 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
657 [super drawInParentContext: ctx fill: fill];
659 CGRect frame = self.frame;
660 const CGFloat midx=floor(CGRectGetMidX(frame))+0.5,
661 midy=floor(CGRectGetMidY(frame))+0.5;
662 CGPoint p[4] = {{CGRectGetMinX(frame),midy},
663 {CGRectGetMaxX(frame),midy},
664 {midx,CGRectGetMinY(frame)},
665 {midx,CGRectGetMaxY(frame)}};
666 if( ! self.s ) p[2].y = midy;
667 if( ! self.n ) p[3].y = midy;
668 if( ! self.w ) p[0].x = midx;
669 if( ! self.e ) p[1].x = midx;
670 CGContextStrokeLineSegments(ctx, p, 4);
673 CGContextSetFillColorWithColor(ctx,_grid.lineColor);
674 CGRect dot = CGRectMake(midx-2.5, midy-2.5, 5, 5);
675 CGContextFillEllipseInRect(ctx, dot);