Added new convenience methods for Game implementations.
1.1 --- a/Source/Bit.h Sat Jul 05 17:46:43 2008 -0700
1.2 +++ b/Source/Bit.h Mon Jul 07 15:47:42 2008 -0700
1.3 @@ -47,6 +47,7 @@
1.4 CGSize _restingShadowOffset;
1.5 BOOL _pickedUp;
1.6 Player *_owner; // Player that owns this Bit
1.7 + NSInteger _tag;
1.8 }
1.9
1.10 /** Conveniences for getting/setting the layer's scale and rotation */
1.11 @@ -66,6 +67,9 @@
1.12 @property (readonly, getter=isFriendly) BOOL friendly;
1.13 @property (readonly, getter=isUnfriendly) BOOL unfriendly;
1.14
1.15 +/** An uninterpreted integer that the Game can use for its own purposes. */
1.16 +@property NSInteger tag;
1.17 +
1.18 /** Removes this Bit while running a explosion/fade-out animation */
1.19 - (void) destroy;
1.20
2.1 --- a/Source/Bit.m Sat Jul 05 17:46:43 2008 -0700
2.2 +++ b/Source/Bit.m Mon Jul 07 15:47:42 2008 -0700
2.3 @@ -42,6 +42,7 @@
2.4 {
2.5 Bit *clone = [super copyWithZone: zone];
2.6 clone->_owner = _owner;
2.7 + clone->_tag = _tag;
2.8 return clone;
2.9 }
2.10
2.11 @@ -51,7 +52,7 @@
2.12 }
2.13
2.14
2.15 -@synthesize owner=_owner;
2.16 +@synthesize owner=_owner, tag=_tag;
2.17
2.18 - (BOOL) isFriendly {return _owner.friendly;}
2.19 - (BOOL) isUnfriendly {return _owner.unfriendly;}
3.1 --- a/Source/BitHolder.h Sat Jul 05 17:46:43 2008 -0700
3.2 +++ b/Source/BitHolder.h Mon Jul 07 15:47:42 2008 -0700
3.3 @@ -39,6 +39,9 @@
3.4 /** BitHolders will be highlighted while the target of a drag operation */
3.5 @property BOOL highlighted;
3.6
3.7 +/** An uninterpreted integer that the Game can use for its own purposes. */
3.8 +@property NSInteger tag;
3.9 +
3.10
3.11 /** Tests whether the bit is allowed to be dragged out of me.
3.12 Returns the input bit, or possibly a different Bit to drag instead, or nil if not allowed.
3.13 @@ -71,6 +74,7 @@
3.14 @protected
3.15 Bit *_bit;
3.16 BOOL _highlighted;
3.17 + NSInteger _tag;
3.18 }
3.19
3.20 @end
4.1 --- a/Source/BitHolder.m Sat Jul 05 17:46:43 2008 -0700
4.2 +++ b/Source/BitHolder.m Mon Jul 07 15:47:42 2008 -0700
4.3 @@ -67,7 +67,7 @@
4.4
4.5 - (BOOL) isEmpty {return self.bit==nil;}
4.6
4.7 -@synthesize highlighted=_highlighted;
4.8 +@synthesize highlighted=_highlighted, tag=_tag;
4.9
4.10 - (Bit*) canDragBit: (Bit*)bit
4.11 {
5.1 --- a/Source/BoardView.m Sat Jul 05 17:46:43 2008 -0700
5.2 +++ b/Source/BoardView.m Mon Jul 07 15:47:42 2008 -0700
5.3 @@ -24,6 +24,7 @@
5.4 #import "Bit.h"
5.5 #import "BitHolder.h"
5.6 #import "Game.h"
5.7 +#import "Turn.h"
5.8 #import "Player.h"
5.9 #import "QuartzUtils.h"
5.10 #import "GGBUtils.h"
5.11 @@ -103,7 +104,9 @@
5.12
5.13 - (BOOL) canMakeMove
5.14 {
5.15 - return (_game && _game.currentPlayer.local && _game.currentTurnNo==_game.maxTurnNo);
5.16 + return _game != nil
5.17 + && _game.currentPlayer.local
5.18 + && _game.currentTurn.status < kTurnComplete;
5.19 }
5.20
5.21
6.1 --- a/Source/CheckersGame.h Sat Jul 05 17:46:43 2008 -0700
6.2 +++ b/Source/CheckersGame.h Mon Jul 07 15:47:42 2008 -0700
6.3 @@ -28,9 +28,7 @@
6.4 See: http://en.wikipedia.org/wiki/Draughts */
6.5 @interface CheckersGame : Game
6.6 {
6.7 - int _numPieces[2];
6.8 Grid *_grid;
6.9 - NSMutableArray *_cells;
6.10 }
6.11
6.12 @end
7.1 --- a/Source/CheckersGame.m Sat Jul 05 17:46:43 2008 -0700
7.2 +++ b/Source/CheckersGame.m Mon Jul 07 15:47:42 2008 -0700
7.3 @@ -53,7 +53,6 @@
7.4 {
7.5 self = [super init];
7.6 if (self != nil) {
7.7 - _cells = [[NSMutableArray alloc] init];
7.8 [self setNumberOfPlayers: 2];
7.9
7.10 PreloadSound(@"Tink");
7.11 @@ -82,7 +81,7 @@
7.12 - (void) makeKing: (Piece*)piece
7.13 {
7.14 piece.scale = 1.4;
7.15 - [piece setValue: @"King" forKey: @"King"];
7.16 + piece.tag = YES; // tag property stores the 'king' flag
7.17 piece.name = piece.owner.index ?@"4" :@"3";
7.18 }
7.19
7.20 @@ -100,65 +99,34 @@
7.21 grid.altCellColor = CreateGray(1.0, 0.25);
7.22 grid.lineColor = nil;
7.23
7.24 - [grid addAllCells];
7.25 - [_cells removeAllObjects];
7.26 for( int i=0; i<32; i++ ) {
7.27 int row = i/4;
7.28 - [_cells addObject: [_grid cellAtRow: row column: 2*(i%4) + (row&1)]];
7.29 + [_grid addCellAtRow: row column: 2*(i%4) + (row&1)];
7.30 }
7.31 + [_grid release]; // its superlayer still retains it
7.32 }
7.33
7.34 -- (void) dealloc
7.35 +- (NSString*) initialStateString {return @"111111111111--------222222222222";}
7.36 +- (NSString*) stateString {return _grid.stateString;}
7.37 +- (void) setStateString: (NSString*)state {_grid.stateString = state;}
7.38 +
7.39 +- (Piece*) makePieceNamed: (NSString*)name
7.40 {
7.41 - [_cells release];
7.42 - [_grid release];
7.43 - [super dealloc];
7.44 -}
7.45 -
7.46 -
7.47 -- (NSString*) initialStateString
7.48 -{
7.49 - return @"111111111111--------222222222222";
7.50 -}
7.51 -
7.52 -- (NSString*) stateString
7.53 -{
7.54 - unichar state[_cells.count];
7.55 - int i = 0;
7.56 - for( GridCell *cell in _cells ) {
7.57 - NSString *ident = cell.bit.name;
7.58 - if( ident )
7.59 - state[i++] = [ident characterAtIndex: 0];
7.60 - else
7.61 - state[i++] = '-';
7.62 - }
7.63 - return [NSString stringWithCharacters: state length: i];
7.64 -}
7.65 -
7.66 -- (void) setStateString: (NSString*)state
7.67 -{
7.68 - _numPieces[0] = _numPieces[1] = 0;
7.69 - int i = 0;
7.70 - for( GridCell *cell in _cells ) {
7.71 - Piece *piece;
7.72 - int which = [state characterAtIndex: i++] - '1';
7.73 - if( which >=0 && which < 4 ) {
7.74 - int player = (which & 1);
7.75 - piece = [self pieceForPlayer: player];
7.76 - _numPieces[player]++;
7.77 - if( which & 2 )
7.78 - [self makeKing: piece];
7.79 - } else
7.80 - piece = nil;
7.81 - cell.bit = piece;
7.82 - }
7.83 + int which = [name characterAtIndex: 0] - '1';
7.84 + if( which >=0 && which < 4 ) {
7.85 + Piece *piece = [self pieceForPlayer: (which & 1)];
7.86 + if( which & 2 )
7.87 + [self makeKing: piece];
7.88 + return piece;
7.89 + } else
7.90 + return nil;
7.91 }
7.92
7.93
7.94 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)srcHolder to: (id<BitHolder>)dstHolder
7.95 {
7.96 Square *src=(Square*)srcHolder, *dst=(Square*)dstHolder;
7.97 - if( [bit valueForKey: @"King"] )
7.98 + if( bit.tag )
7.99 if( dst==src.bl || dst==src.br || dst==src.l || dst==src.r
7.100 || (src.bl.bit.unfriendly && dst==src.bl.bl) || (src.br.bit.unfriendly && dst==src.br.br) )
7.101 return YES;
7.102 @@ -177,34 +145,24 @@
7.103 [turn addToMove: @"-"];
7.104 [turn addToMove: dst.name];
7.105
7.106 - BOOL isKing = ([bit valueForKey: @"King"] != nil);
7.107 + BOOL isKing = bit.tag;
7.108 PlaySound(isKing ?@"Funk" :@"Tink");
7.109
7.110 // "King" a piece that made it to the last row:
7.111 - if( dst.row == (playerIndex ?0 :7) )
7.112 - if( ! isKing ) {
7.113 - PlaySound(@"Blow");
7.114 - [self makeKing: (Piece*)bit];
7.115 - [turn addToMove: @"*"];
7.116 - // don't set isKing flag - piece can't jump again after being kinged.
7.117 - }
7.118 + if( !isKing && (dst.row == (playerIndex ?0 :7)) ) {
7.119 + PlaySound(@"Blow");
7.120 + [self makeKing: (Piece*)bit];
7.121 + [turn addToMove: @"*"];
7.122 + // don't set isKing flag - piece can't jump again after being kinged.
7.123 + }
7.124
7.125 // Check for a capture:
7.126 - Square *capture = nil;
7.127 - if(dst==src.fl.fl)
7.128 - capture = src.fl;
7.129 - else if(dst==src.fr.fr)
7.130 - capture = src.fr;
7.131 - else if(dst==src.bl.bl)
7.132 - capture = src.bl;
7.133 - else if(dst==src.br.br)
7.134 - capture = src.br;
7.135 -
7.136 - if( capture ) {
7.137 - PlaySound(@"Pop");
7.138 - _numPieces[capture.bit.owner.index]--;
7.139 + NSArray *line = [src lineToCell: dst inclusive: NO];
7.140 + if( line.count==1 ) {
7.141 + Square *capture = [line objectAtIndex: 0];
7.142 [capture destroyBit];
7.143 [turn addToMove: @"!"];
7.144 + PlaySound(@"Pop");
7.145
7.146 // Now check if another capture is possible. If so, don't end the turn:
7.147 if( (dst.fl.bit.unfriendly && dst.fl.fl.empty) || (dst.fr.bit.unfriendly && dst.fr.fr.empty) )
7.148 @@ -219,11 +177,9 @@
7.149
7.150 - (Player*) checkForWinner
7.151 {
7.152 - // Whoever runs out of pieces loses:
7.153 - if( _numPieces[0]==0 )
7.154 - return [self.players objectAtIndex: 1];
7.155 - else if( _numPieces[1]==0 )
7.156 - return [self.players objectAtIndex: 0];
7.157 + NSCountedSet *remaining = _grid.countPiecesByPlayer;
7.158 + if( remaining.count==1 )
7.159 + return [remaining anyObject];
7.160 else
7.161 return nil;
7.162 }
8.1 --- a/Source/GGBUtils.m Sat Jul 05 17:46:43 2008 -0700
8.2 +++ b/Source/GGBUtils.m Mon Jul 07 15:47:42 2008 -0700
8.3 @@ -25,6 +25,7 @@
8.4 #endif
8.5
8.6
8.7 +#ifndef _MYUTILITIES_COLLECTIONUTILS_
8.8 void setObj( id *variable, id newValue )
8.9 {
8.10 if( *variable != newValue ) {
8.11 @@ -40,6 +41,7 @@
8.12 *variable = [(id)newValue copy];
8.13 }
8.14 }
8.15 +#endif
8.16
8.17
8.18 void DelayFor( NSTimeInterval interval )
9.1 --- a/Source/Game+Protected.h Sat Jul 05 17:46:43 2008 -0700
9.2 +++ b/Source/Game+Protected.h Mon Jul 07 15:47:42 2008 -0700
9.3 @@ -12,6 +12,7 @@
9.4 #import "Turn.h"
9.5 #import "Bit.h"
9.6 #import "BitHolder.h"
9.7 +@class Piece;
9.8
9.9
9.10 /** Game API for subclasses to use / override */
9.11 @@ -62,4 +63,8 @@
9.12 The string must have been returned by -currentMove at some point. */
9.13 - (BOOL) applyMoveString: (NSString*)move;
9.14
9.15 +/** An optional method called as a subroutine by -[Grid setStateString:].
9.16 + If you decide to call that in your -setStateString: implementation, you need to implement this too. */
9.17 +- (Piece*) makePieceNamed: (NSString*)name;
9.18 +
9.19 @end
10.1 --- a/Source/Grid.h Sat Jul 05 17:46:43 2008 -0700
10.2 +++ b/Source/Grid.h Mon Jul 07 15:47:42 2008 -0700
10.3 @@ -73,6 +73,21 @@
10.4
10.5 - (GridCell*) cellWithName: (NSString*)identifier;
10.6
10.7 +/** Returns all of the Players who have any Bits on the grid, with each Player's count being the
10.8 + number of Bits. */
10.9 +- (NSCountedSet*) countPiecesByPlayer;
10.10 +
10.11 +
10.12 +/** Utility to get and set the entire state of the Grid. The stateString is made by concatenating
10.13 + the name of the Bit of every GridCell in order, with "-" for empty cells.
10.14 + The setter method calls the Game's optional -makePieceNamed: method to create the pieces. */
10.15 +@property (copy) NSString *stateString;
10.16 +
10.17 +/** Interprets the string as a series of cell names separated by "-", and tells the Game to move
10.18 + the piece at the first cell to each cell in succession by calling its -animateMoveFrom:to:. */
10.19 +- (BOOL) applyMoveString: (NSString*)move;
10.20 +
10.21 +
10.22 // protected:
10.23 - (GridCell*) createCellAtRow: (unsigned)row column: (unsigned)col
10.24 suggestedFrame: (CGRect)frame;
10.25 @@ -98,7 +113,7 @@
10.26 /** Returns YES if 'forward' is north (increasing row#) for the current player */
10.27 @property (readonly) BOOL fwdIsN;
10.28
10.29 -/* Go-style group detection. Returns the set of contiguous GridCells that have pieces of the same
10.30 +/** Go-style group detection. Returns the set of contiguous GridCells that have pieces of the same
10.31 owner as this one, and optionally a count of the number of "liberties", or adjacent empty cells. */
10.32 - (NSSet*) getGroup: (int*)outLiberties;
10.33
10.34 @@ -128,6 +143,17 @@
10.35 @property (readonly) Square *nw, *n, *ne, *e, *se, *s, *sw, *w; // Absolute directions (n = increasing row#)
10.36 @property (readonly) Square *fl, *f, *fr, *r, *br, *b, *bl, *l; // Relative to player (upside-down for player 2)
10.37
10.38 +/** Returns the absolute direction selector (see above) for the straight line from self to dst;
10.39 + or NULL if there is no straight line, or if dst==self.
10.40 + Diagonal lines are allowed only if the Grid's -usesDiagonals is YES. */
10.41 +- (SEL) directionToCell: (GridCell*)dst;
10.42 +
10.43 +/** Returns an array of all the cells in a straight line from self to dst;
10.44 + or NULL if there is no straight line, or if dst==self.
10.45 + If 'inclusive' is YES, the array will include self and dst, otherwise not.
10.46 + Diagonal lines are allowed only if the Grid's -usesDiagonals is YES. */
10.47 +- (NSArray*) lineToCell: (GridCell*)dst inclusive: (BOOL)inclusive;
10.48 +
10.49 @end
10.50
10.51
11.1 --- a/Source/Grid.m Sat Jul 05 17:46:43 2008 -0700
11.2 +++ b/Source/Grid.m Mon Jul 07 15:47:42 2008 -0700
11.3 @@ -22,7 +22,8 @@
11.4 */
11.5 #import "Grid.h"
11.6 #import "Bit.h"
11.7 -#import "Game.h"
11.8 +#import "Piece.h"
11.9 +#import "Game+Protected.h"
11.10 #import "Player.h"
11.11 #import "QuartzUtils.h"
11.12
11.13 @@ -112,8 +113,7 @@
11.14 }
11.15
11.16 @synthesize cellClass=_cellClass, rows=_nRows, columns=_nColumns, spacing=_spacing,
11.17 - usesDiagonals=_usesDiagonals, allowsMoves=_allowsMoves, allowsCaptures=_allowsCaptures,
11.18 - cells=_cells;
11.19 + usesDiagonals=_usesDiagonals, allowsMoves=_allowsMoves, allowsCaptures=_allowsCaptures;
11.20
11.21
11.22 #pragma mark -
11.23 @@ -184,6 +184,16 @@
11.24 }
11.25
11.26
11.27 +- (NSArray*) cells
11.28 +{
11.29 + NSMutableArray *cells = [_cells mutableCopy];
11.30 + for( int i=cells.count-1; i>=0; i-- )
11.31 + if( [cells objectAtIndex: i] == [NSNull null] )
11.32 + [cells removeObjectAtIndex: i];
11.33 + return cells;
11.34 +}
11.35 +
11.36 +
11.37 - (GridCell*) cellWithName: (NSString*)name
11.38 {
11.39 for( CALayer *layer in self.sublayers )
11.40 @@ -194,6 +204,61 @@
11.41 }
11.42
11.43
11.44 +- (NSCountedSet*) countPiecesByPlayer
11.45 +{
11.46 + NSCountedSet *players = [NSCountedSet set];
11.47 + for( GridCell *cell in self.cells ) {
11.48 + Player *owner = cell.bit.owner;
11.49 + if( owner )
11.50 + [players addObject: owner];
11.51 + }
11.52 + return players;
11.53 +}
11.54 +
11.55 +
11.56 +
11.57 +#pragma mark -
11.58 +#pragma mark GAME STATE:
11.59 +
11.60 +
11.61 +- (NSString*) stateString
11.62 +{
11.63 + NSMutableString *state = [NSMutableString stringWithCapacity: _cells.count];
11.64 + for( GridCell *cell in self.cells ) {
11.65 + Bit *bit = cell.bit;
11.66 + NSString *name = bit ?bit.name :@"-";
11.67 + NSAssert(name.length==1,@"Missing or multicharacter name");
11.68 + [state appendString: name];
11.69 + }
11.70 + return state;
11.71 +}
11.72 +
11.73 +- (void) setStateString: (NSString*)state
11.74 +{
11.75 + Game *game = self.game;
11.76 + int i = 0;
11.77 + for( GridCell *cell in self.cells )
11.78 + cell.bit = [game makePieceNamed: [state substringWithRange: NSMakeRange(i++,1)]];
11.79 +}
11.80 +
11.81 +
11.82 +- (BOOL) applyMoveString: (NSString*)move
11.83 +{
11.84 + GridCell *src = nil;
11.85 + for( NSString *ident in [move componentsSeparatedByString: @"-"] ) {
11.86 + while( [ident hasSuffix: @"!"] || [ident hasSuffix: @"*"] )
11.87 + ident = [ident substringToIndex: ident.length-1];
11.88 + GridCell *dst = [self cellWithName: ident];
11.89 + if( dst == nil )
11.90 + return NO;
11.91 + if( src && ! [self.game animateMoveFrom: src to: dst] )
11.92 + return NO;
11.93 + src = dst;
11.94 + }
11.95 + return YES;
11.96 +}
11.97 +
11.98 +
11.99 #pragma mark -
11.100 #pragma mark DRAWING:
11.101
11.102 @@ -464,6 +529,41 @@
11.103 - (Square*) l {return self.fwdIsN ?self.w :self.e;}
11.104
11.105
11.106 +static int sgn( int n ) {return n<0 ?-1 :(n>0 ?1 :0);}
11.107 +
11.108 +
11.109 +- (SEL) directionToCell: (GridCell*)dst
11.110 +{
11.111 + static NSString* const kDirections[9] = {@"sw", @"s", @"se",
11.112 + @"w", nil, @"e",
11.113 + @"nw", @"n", @"ne"};
11.114 + if( dst.grid != self.grid )
11.115 + return NULL;
11.116 + int dy=dst.row-_row, dx=dst.column-_column;
11.117 + if( dx && dy )
11.118 + if( !( _grid.usesDiagonals && abs(dx)==abs(dy) ) )
11.119 + return NULL;
11.120 + NSString *dir = kDirections[ 3*(sgn(dy)+1) + (sgn(dx)+1) ];
11.121 + return dir ?NSSelectorFromString(dir) :NULL;
11.122 +}
11.123 +
11.124 +- (NSArray*) lineToCell: (GridCell*)dst inclusive: (BOOL)inclusive;
11.125 +{
11.126 + SEL dir = [self directionToCell: dst];
11.127 + if( ! dir )
11.128 + return nil;
11.129 + NSMutableArray *line = [NSMutableArray array];
11.130 + GridCell *cell;
11.131 + for( cell=self; cell; cell = [cell performSelector: dir] ) {
11.132 + if( inclusive || (cell!=self && cell!=dst) )
11.133 + [line addObject: cell];
11.134 + if( cell==dst )
11.135 + return line;
11.136 + }
11.137 + return nil; // should be impossible, but just in case
11.138 +}
11.139 +
11.140 +
11.141 #if ! TARGET_OS_IPHONE
11.142
11.143 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
12.1 --- a/Source/HexchequerGame.m Sat Jul 05 17:46:43 2008 -0700
12.2 +++ b/Source/HexchequerGame.m Mon Jul 07 15:47:42 2008 -0700
12.3 @@ -43,14 +43,6 @@
12.4 grid.cellColor = CreateGray(1.0, 0.25);
12.5 grid.lineColor = kTranslucentLightGrayColor;
12.6 [grid addCellsInHexagon];
12.7 - [_cells removeAllObjects];
12.8 - for( int y=0; y<9; y++ ) {
12.9 - for( int x=0; x<9; x++ ) {
12.10 - GridCell *cell = [_grid cellAtRow: y column: x];
12.11 - if( cell )
12.12 - [_cells addObject: cell];
12.13 - }
12.14 - }
12.15 }
12.16
12.17
12.18 @@ -113,7 +105,6 @@
12.19 if( capture ) {
12.20 PlaySound(@"Pop");
12.21 [turn addToMove: @"!"];
12.22 - _numPieces[capture.bit.owner.index]--;
12.23 [capture destroyBit];
12.24
12.25 // Now check if another capture is possible. If so, don't end the turn: