# HG changeset patch # User Jens Alfke # Date 1215470862 25200 # Node ID 4e567e11f45f01cae5749585cc631dd9c25262a6 # Parent 436cbdf5681030533a554d94af14cfcdebf60cf3 Added new convenience methods for Game implementations. diff -r 436cbdf56810 -r 4e567e11f45f Source/Bit.h --- a/Source/Bit.h Sat Jul 05 17:46:43 2008 -0700 +++ b/Source/Bit.h Mon Jul 07 15:47:42 2008 -0700 @@ -47,6 +47,7 @@ CGSize _restingShadowOffset; BOOL _pickedUp; Player *_owner; // Player that owns this Bit + NSInteger _tag; } /** Conveniences for getting/setting the layer's scale and rotation */ @@ -66,6 +67,9 @@ @property (readonly, getter=isFriendly) BOOL friendly; @property (readonly, getter=isUnfriendly) BOOL unfriendly; +/** An uninterpreted integer that the Game can use for its own purposes. */ +@property NSInteger tag; + /** Removes this Bit while running a explosion/fade-out animation */ - (void) destroy; diff -r 436cbdf56810 -r 4e567e11f45f Source/Bit.m --- a/Source/Bit.m Sat Jul 05 17:46:43 2008 -0700 +++ b/Source/Bit.m Mon Jul 07 15:47:42 2008 -0700 @@ -42,6 +42,7 @@ { Bit *clone = [super copyWithZone: zone]; clone->_owner = _owner; + clone->_tag = _tag; return clone; } @@ -51,7 +52,7 @@ } -@synthesize owner=_owner; +@synthesize owner=_owner, tag=_tag; - (BOOL) isFriendly {return _owner.friendly;} - (BOOL) isUnfriendly {return _owner.unfriendly;} diff -r 436cbdf56810 -r 4e567e11f45f Source/BitHolder.h --- a/Source/BitHolder.h Sat Jul 05 17:46:43 2008 -0700 +++ b/Source/BitHolder.h Mon Jul 07 15:47:42 2008 -0700 @@ -39,6 +39,9 @@ /** BitHolders will be highlighted while the target of a drag operation */ @property BOOL highlighted; +/** An uninterpreted integer that the Game can use for its own purposes. */ +@property NSInteger tag; + /** Tests whether the bit is allowed to be dragged out of me. Returns the input bit, or possibly a different Bit to drag instead, or nil if not allowed. @@ -71,6 +74,7 @@ @protected Bit *_bit; BOOL _highlighted; + NSInteger _tag; } @end diff -r 436cbdf56810 -r 4e567e11f45f Source/BitHolder.m --- a/Source/BitHolder.m Sat Jul 05 17:46:43 2008 -0700 +++ b/Source/BitHolder.m Mon Jul 07 15:47:42 2008 -0700 @@ -67,7 +67,7 @@ - (BOOL) isEmpty {return self.bit==nil;} -@synthesize highlighted=_highlighted; +@synthesize highlighted=_highlighted, tag=_tag; - (Bit*) canDragBit: (Bit*)bit { diff -r 436cbdf56810 -r 4e567e11f45f Source/BoardView.m --- a/Source/BoardView.m Sat Jul 05 17:46:43 2008 -0700 +++ b/Source/BoardView.m Mon Jul 07 15:47:42 2008 -0700 @@ -24,6 +24,7 @@ #import "Bit.h" #import "BitHolder.h" #import "Game.h" +#import "Turn.h" #import "Player.h" #import "QuartzUtils.h" #import "GGBUtils.h" @@ -103,7 +104,9 @@ - (BOOL) canMakeMove { - return (_game && _game.currentPlayer.local && _game.currentTurnNo==_game.maxTurnNo); + return _game != nil + && _game.currentPlayer.local + && _game.currentTurn.status < kTurnComplete; } diff -r 436cbdf56810 -r 4e567e11f45f Source/CheckersGame.h --- a/Source/CheckersGame.h Sat Jul 05 17:46:43 2008 -0700 +++ b/Source/CheckersGame.h Mon Jul 07 15:47:42 2008 -0700 @@ -28,9 +28,7 @@ See: http://en.wikipedia.org/wiki/Draughts */ @interface CheckersGame : Game { - int _numPieces[2]; Grid *_grid; - NSMutableArray *_cells; } @end diff -r 436cbdf56810 -r 4e567e11f45f Source/CheckersGame.m --- a/Source/CheckersGame.m Sat Jul 05 17:46:43 2008 -0700 +++ b/Source/CheckersGame.m Mon Jul 07 15:47:42 2008 -0700 @@ -53,7 +53,6 @@ { self = [super init]; if (self != nil) { - _cells = [[NSMutableArray alloc] init]; [self setNumberOfPlayers: 2]; PreloadSound(@"Tink"); @@ -82,7 +81,7 @@ - (void) makeKing: (Piece*)piece { piece.scale = 1.4; - [piece setValue: @"King" forKey: @"King"]; + piece.tag = YES; // tag property stores the 'king' flag piece.name = piece.owner.index ?@"4" :@"3"; } @@ -100,65 +99,34 @@ grid.altCellColor = CreateGray(1.0, 0.25); grid.lineColor = nil; - [grid addAllCells]; - [_cells removeAllObjects]; for( int i=0; i<32; i++ ) { int row = i/4; - [_cells addObject: [_grid cellAtRow: row column: 2*(i%4) + (row&1)]]; + [_grid addCellAtRow: row column: 2*(i%4) + (row&1)]; } + [_grid release]; // its superlayer still retains it } -- (void) dealloc +- (NSString*) initialStateString {return @"111111111111--------222222222222";} +- (NSString*) stateString {return _grid.stateString;} +- (void) setStateString: (NSString*)state {_grid.stateString = state;} + +- (Piece*) makePieceNamed: (NSString*)name { - [_cells release]; - [_grid release]; - [super dealloc]; -} - - -- (NSString*) initialStateString -{ - return @"111111111111--------222222222222"; -} - -- (NSString*) stateString -{ - unichar state[_cells.count]; - int i = 0; - for( GridCell *cell in _cells ) { - NSString *ident = cell.bit.name; - if( ident ) - state[i++] = [ident characterAtIndex: 0]; - else - state[i++] = '-'; - } - return [NSString stringWithCharacters: state length: i]; -} - -- (void) setStateString: (NSString*)state -{ - _numPieces[0] = _numPieces[1] = 0; - int i = 0; - for( GridCell *cell in _cells ) { - Piece *piece; - int which = [state characterAtIndex: i++] - '1'; - if( which >=0 && which < 4 ) { - int player = (which & 1); - piece = [self pieceForPlayer: player]; - _numPieces[player]++; - if( which & 2 ) - [self makeKing: piece]; - } else - piece = nil; - cell.bit = piece; - } + int which = [name characterAtIndex: 0] - '1'; + if( which >=0 && which < 4 ) { + Piece *piece = [self pieceForPlayer: (which & 1)]; + if( which & 2 ) + [self makeKing: piece]; + return piece; + } else + return nil; } - (BOOL) canBit: (Bit*)bit moveFrom: (id)srcHolder to: (id)dstHolder { Square *src=(Square*)srcHolder, *dst=(Square*)dstHolder; - if( [bit valueForKey: @"King"] ) + if( bit.tag ) if( dst==src.bl || dst==src.br || dst==src.l || dst==src.r || (src.bl.bit.unfriendly && dst==src.bl.bl) || (src.br.bit.unfriendly && dst==src.br.br) ) return YES; @@ -177,34 +145,24 @@ [turn addToMove: @"-"]; [turn addToMove: dst.name]; - BOOL isKing = ([bit valueForKey: @"King"] != nil); + BOOL isKing = bit.tag; PlaySound(isKing ?@"Funk" :@"Tink"); // "King" a piece that made it to the last row: - if( dst.row == (playerIndex ?0 :7) ) - if( ! isKing ) { - PlaySound(@"Blow"); - [self makeKing: (Piece*)bit]; - [turn addToMove: @"*"]; - // don't set isKing flag - piece can't jump again after being kinged. - } + if( !isKing && (dst.row == (playerIndex ?0 :7)) ) { + PlaySound(@"Blow"); + [self makeKing: (Piece*)bit]; + [turn addToMove: @"*"]; + // don't set isKing flag - piece can't jump again after being kinged. + } // Check for a capture: - Square *capture = nil; - if(dst==src.fl.fl) - capture = src.fl; - else if(dst==src.fr.fr) - capture = src.fr; - else if(dst==src.bl.bl) - capture = src.bl; - else if(dst==src.br.br) - capture = src.br; - - if( capture ) { - PlaySound(@"Pop"); - _numPieces[capture.bit.owner.index]--; + NSArray *line = [src lineToCell: dst inclusive: NO]; + if( line.count==1 ) { + Square *capture = [line objectAtIndex: 0]; [capture destroyBit]; [turn addToMove: @"!"]; + PlaySound(@"Pop"); // Now check if another capture is possible. If so, don't end the turn: if( (dst.fl.bit.unfriendly && dst.fl.fl.empty) || (dst.fr.bit.unfriendly && dst.fr.fr.empty) ) @@ -219,11 +177,9 @@ - (Player*) checkForWinner { - // Whoever runs out of pieces loses: - if( _numPieces[0]==0 ) - return [self.players objectAtIndex: 1]; - else if( _numPieces[1]==0 ) - return [self.players objectAtIndex: 0]; + NSCountedSet *remaining = _grid.countPiecesByPlayer; + if( remaining.count==1 ) + return [remaining anyObject]; else return nil; } diff -r 436cbdf56810 -r 4e567e11f45f Source/GGBUtils.m --- a/Source/GGBUtils.m Sat Jul 05 17:46:43 2008 -0700 +++ b/Source/GGBUtils.m Mon Jul 07 15:47:42 2008 -0700 @@ -25,6 +25,7 @@ #endif +#ifndef _MYUTILITIES_COLLECTIONUTILS_ void setObj( id *variable, id newValue ) { if( *variable != newValue ) { @@ -40,6 +41,7 @@ *variable = [(id)newValue copy]; } } +#endif void DelayFor( NSTimeInterval interval ) diff -r 436cbdf56810 -r 4e567e11f45f Source/Game+Protected.h --- a/Source/Game+Protected.h Sat Jul 05 17:46:43 2008 -0700 +++ b/Source/Game+Protected.h Mon Jul 07 15:47:42 2008 -0700 @@ -12,6 +12,7 @@ #import "Turn.h" #import "Bit.h" #import "BitHolder.h" +@class Piece; /** Game API for subclasses to use / override */ @@ -62,4 +63,8 @@ The string must have been returned by -currentMove at some point. */ - (BOOL) applyMoveString: (NSString*)move; +/** An optional method called as a subroutine by -[Grid setStateString:]. + If you decide to call that in your -setStateString: implementation, you need to implement this too. */ +- (Piece*) makePieceNamed: (NSString*)name; + @end diff -r 436cbdf56810 -r 4e567e11f45f Source/Grid.h --- a/Source/Grid.h Sat Jul 05 17:46:43 2008 -0700 +++ b/Source/Grid.h Mon Jul 07 15:47:42 2008 -0700 @@ -73,6 +73,21 @@ - (GridCell*) cellWithName: (NSString*)identifier; +/** Returns all of the Players who have any Bits on the grid, with each Player's count being the + number of Bits. */ +- (NSCountedSet*) countPiecesByPlayer; + + +/** Utility to get and set the entire state of the Grid. The stateString is made by concatenating + the name of the Bit of every GridCell in order, with "-" for empty cells. + The setter method calls the Game's optional -makePieceNamed: method to create the pieces. */ +@property (copy) NSString *stateString; + +/** Interprets the string as a series of cell names separated by "-", and tells the Game to move + the piece at the first cell to each cell in succession by calling its -animateMoveFrom:to:. */ +- (BOOL) applyMoveString: (NSString*)move; + + // protected: - (GridCell*) createCellAtRow: (unsigned)row column: (unsigned)col suggestedFrame: (CGRect)frame; @@ -98,7 +113,7 @@ /** Returns YES if 'forward' is north (increasing row#) for the current player */ @property (readonly) BOOL fwdIsN; -/* Go-style group detection. Returns the set of contiguous GridCells that have pieces of the same +/** Go-style group detection. Returns the set of contiguous GridCells that have pieces of the same owner as this one, and optionally a count of the number of "liberties", or adjacent empty cells. */ - (NSSet*) getGroup: (int*)outLiberties; @@ -128,6 +143,17 @@ @property (readonly) Square *nw, *n, *ne, *e, *se, *s, *sw, *w; // Absolute directions (n = increasing row#) @property (readonly) Square *fl, *f, *fr, *r, *br, *b, *bl, *l; // Relative to player (upside-down for player 2) +/** Returns the absolute direction selector (see above) for the straight line from self to dst; + or NULL if there is no straight line, or if dst==self. + Diagonal lines are allowed only if the Grid's -usesDiagonals is YES. */ +- (SEL) directionToCell: (GridCell*)dst; + +/** Returns an array of all the cells in a straight line from self to dst; + or NULL if there is no straight line, or if dst==self. + If 'inclusive' is YES, the array will include self and dst, otherwise not. + Diagonal lines are allowed only if the Grid's -usesDiagonals is YES. */ +- (NSArray*) lineToCell: (GridCell*)dst inclusive: (BOOL)inclusive; + @end diff -r 436cbdf56810 -r 4e567e11f45f Source/Grid.m --- a/Source/Grid.m Sat Jul 05 17:46:43 2008 -0700 +++ b/Source/Grid.m Mon Jul 07 15:47:42 2008 -0700 @@ -22,7 +22,8 @@ */ #import "Grid.h" #import "Bit.h" -#import "Game.h" +#import "Piece.h" +#import "Game+Protected.h" #import "Player.h" #import "QuartzUtils.h" @@ -112,8 +113,7 @@ } @synthesize cellClass=_cellClass, rows=_nRows, columns=_nColumns, spacing=_spacing, - usesDiagonals=_usesDiagonals, allowsMoves=_allowsMoves, allowsCaptures=_allowsCaptures, - cells=_cells; + usesDiagonals=_usesDiagonals, allowsMoves=_allowsMoves, allowsCaptures=_allowsCaptures; #pragma mark - @@ -184,6 +184,16 @@ } +- (NSArray*) cells +{ + NSMutableArray *cells = [_cells mutableCopy]; + for( int i=cells.count-1; i>=0; i-- ) + if( [cells objectAtIndex: i] == [NSNull null] ) + [cells removeObjectAtIndex: i]; + return cells; +} + + - (GridCell*) cellWithName: (NSString*)name { for( CALayer *layer in self.sublayers ) @@ -194,6 +204,61 @@ } +- (NSCountedSet*) countPiecesByPlayer +{ + NSCountedSet *players = [NSCountedSet set]; + for( GridCell *cell in self.cells ) { + Player *owner = cell.bit.owner; + if( owner ) + [players addObject: owner]; + } + return players; +} + + + +#pragma mark - +#pragma mark GAME STATE: + + +- (NSString*) stateString +{ + NSMutableString *state = [NSMutableString stringWithCapacity: _cells.count]; + for( GridCell *cell in self.cells ) { + Bit *bit = cell.bit; + NSString *name = bit ?bit.name :@"-"; + NSAssert(name.length==1,@"Missing or multicharacter name"); + [state appendString: name]; + } + return state; +} + +- (void) setStateString: (NSString*)state +{ + Game *game = self.game; + int i = 0; + for( GridCell *cell in self.cells ) + cell.bit = [game makePieceNamed: [state substringWithRange: NSMakeRange(i++,1)]]; +} + + +- (BOOL) applyMoveString: (NSString*)move +{ + GridCell *src = nil; + for( NSString *ident in [move componentsSeparatedByString: @"-"] ) { + while( [ident hasSuffix: @"!"] || [ident hasSuffix: @"*"] ) + ident = [ident substringToIndex: ident.length-1]; + GridCell *dst = [self cellWithName: ident]; + if( dst == nil ) + return NO; + if( src && ! [self.game animateMoveFrom: src to: dst] ) + return NO; + src = dst; + } + return YES; +} + + #pragma mark - #pragma mark DRAWING: @@ -464,6 +529,41 @@ - (Square*) l {return self.fwdIsN ?self.w :self.e;} +static int sgn( int n ) {return n<0 ?-1 :(n>0 ?1 :0);} + + +- (SEL) directionToCell: (GridCell*)dst +{ + static NSString* const kDirections[9] = {@"sw", @"s", @"se", + @"w", nil, @"e", + @"nw", @"n", @"ne"}; + if( dst.grid != self.grid ) + return NULL; + int dy=dst.row-_row, dx=dst.column-_column; + if( dx && dy ) + if( !( _grid.usesDiagonals && abs(dx)==abs(dy) ) ) + return NULL; + NSString *dir = kDirections[ 3*(sgn(dy)+1) + (sgn(dx)+1) ]; + return dir ?NSSelectorFromString(dir) :NULL; +} + +- (NSArray*) lineToCell: (GridCell*)dst inclusive: (BOOL)inclusive; +{ + SEL dir = [self directionToCell: dst]; + if( ! dir ) + return nil; + NSMutableArray *line = [NSMutableArray array]; + GridCell *cell; + for( cell=self; cell; cell = [cell performSelector: dir] ) { + if( inclusive || (cell!=self && cell!=dst) ) + [line addObject: cell]; + if( cell==dst ) + return line; + } + return nil; // should be impossible, but just in case +} + + #if ! TARGET_OS_IPHONE - (BOOL)performDragOperation:(id )sender diff -r 436cbdf56810 -r 4e567e11f45f Source/HexchequerGame.m --- a/Source/HexchequerGame.m Sat Jul 05 17:46:43 2008 -0700 +++ b/Source/HexchequerGame.m Mon Jul 07 15:47:42 2008 -0700 @@ -43,14 +43,6 @@ grid.cellColor = CreateGray(1.0, 0.25); grid.lineColor = kTranslucentLightGrayColor; [grid addCellsInHexagon]; - [_cells removeAllObjects]; - for( int y=0; y<9; y++ ) { - for( int x=0; x<9; x++ ) { - GridCell *cell = [_grid cellAtRow: y column: x]; - if( cell ) - [_cells addObject: cell]; - } - } } @@ -113,7 +105,6 @@ if( capture ) { PlaySound(@"Pop"); [turn addToMove: @"!"]; - _numPieces[capture.bit.owner.index]--; [capture destroyBit]; // Now check if another capture is possible. If so, don't end the turn: