# HG changeset patch # User Jens Alfke # Date 1215132270 25200 # Node ID 6c78cc6bd7a65a90965ed7c005b5207aa3cac779 # Parent a59acc68308048ddf98f93d8f12d064f9a6e6376 Lots of reworking. Completed support for game history, including Turn class. Changed Game API around quite a bit. diff -r a59acc683080 -r 6c78cc6bd7a6 English.lproj/MainMenu.nib/designable.nib --- a/English.lproj/MainMenu.nib/designable.nib Thu May 29 15:04:06 2008 -0700 +++ b/English.lproj/MainMenu.nib/designable.nib Thu Jul 03 17:44:30 2008 -0700 @@ -2,14 +2,15 @@ 0 - 9C31 - 644 - 949.26 + 9D34 + 667 + 949.33 352.00 YES - + + YES @@ -461,17 +462,6 @@ - - - YES - YES - - - 1048576 - 2147483647 - - - Checkers @@ -512,6 +502,26 @@ 3 + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Remote Opponent + r + 1048576 + 2147483647 + + + @@ -671,7 +681,7 @@ YES - -2080244224 + -2079981824 0 @@ -690,13 +700,194 @@ NO + + + 292 + {{55, 10}, {103, 17}} + + YES + + 68288064 + 272630784 + Game Timeline: + + LucidaGrande + 1.300000e+01 + 1044 + + + + 6 + System + controlColor + + 3 + MC42NjY2NjY2OQA + + + + 6 + System + controlTextColor + + 3 + MAA + + + + {1084, 617} {{0, 0}, {1440, 878}} + {3.40282e+38, 3.40282e+38} + + 9 + 2 + {{196, 385}, {773, 125}} + -1543503872 + sheet + NSPanel + + + + 256 + + YES + + + 266 + {{157, 93}, {596, 19}} + + YES + + -2072904127 + 272761856 + + + LucidaGrande + 1.100000e+01 + 3100 + + + YES + + 6 + System + textBackgroundColor + + 3 + MQA + + + + 6 + System + textColor + + + + + + + 268 + {{17, 92}, {135, 17}} + + YES + + 68288064 + 71304192 + Your move was: + + + + + + + + + 266 + {{157, 63}, {596, 19}} + + YES + + -1804468671 + 272761856 + + + + YES + + + + + + + 268 + {{17, 62}, {135, 17}} + + YES + + 68288064 + 71304192 + T3Bwb25lbnTigJlzIG1vdmUgaXM6A + + + + + + + + + 265 + {{663, 12}, {97, 32}} + + 1 + YES + + 67239424 + 134217728 + Continue + + + -2038284033 + 129 + + DQ + 200 + 25 + + + + + 265 + {{566, 12}, {97, 32}} + + YES + + 67239424 + 134217728 + Quit + + + -2038284033 + 268435585 + + q + 200 + 25 + + + + {773, 125} + + + {{0, 0}, {1440, 878}} + {3.40282e+38, 3.40282e+38} + @@ -997,6 +1188,86 @@ 404 + + + toggleRemoteOpponent: + + + + 408 + + + + _myMoveURLField + + + + 425 + + + + _opponentsMoveURLField + + + + 426 + + + + _opponentsMoveSheet + + + + 427 + + + + dismissSheet: + + + + 428 + + + + dismissSheet: + + + + 429 + + + + nextKeyView + + + + 430 + + + + nextKeyView + + + + 431 + + + + nextKeyView + + + + 432 + + + + nextKeyView + + + + 433 + @@ -1455,6 +1726,7 @@ YES + @@ -1490,9 +1762,10 @@ - + + @@ -1517,11 +1790,6 @@ - 387 - - - - 393 @@ -1540,6 +1808,137 @@ + + 405 + + + YES + + + + + + 406 + + + + + 387 + + + + + 407 + + + + + 409 + + + YES + + + + + + 410 + + + YES + + + + + + + + + + + 411 + + + YES + + + + + + 412 + + + + + 415 + + + YES + + + + + + 416 + + + + + 417 + + + YES + + + + + + 418 + + + YES + + + + + + 419 + + + + + 420 + + + + + 421 + + + YES + + + + + + 422 + + + + + 423 + + + YES + + + + + + 424 + + + @@ -1642,7 +2041,6 @@ 371.IBWindowTemplateEditedContentRect 371.NSWindowTemplate.visibleAtLaunch 371.editorWindowContentRectSynchronizationRect - 371.lastResizeAction 372.IBPluginDependency 375.IBPluginDependency 376.IBPluginDependency @@ -1659,6 +2057,26 @@ 393.IBPluginDependency 401.IBPluginDependency 402.IBPluginDependency + 405.IBPluginDependency + 406.IBPluginDependency + 407.IBPluginDependency + 409.IBEditorWindowLastContentRect + 409.IBPluginDependency + 409.IBWindowTemplateEditedContentRect + 409.NSWindowTemplate.visibleAtLaunch + 410.IBPluginDependency + 411.IBPluginDependency + 412.IBPluginDependency + 415.IBPluginDependency + 416.IBPluginDependency + 417.IBPluginDependency + 418.IBPluginDependency + 419.IBPluginDependency + 420.IBPluginDependency + 421.IBPluginDependency + 422.IBPluginDependency + 423.IBPluginDependency + 424.IBPluginDependency 5.IBPluginDependency 5.ImportedFromIB2 56.IBPluginDependency @@ -1684,6 +2102,7 @@ 79.ImportedFromIB2 80.IBPluginDependency 80.ImportedFromIB2 + 81.IBEditorWindowLastContentRect 81.IBPluginDependency 81.ImportedFromIB2 81.editorWindowContentRectSynchronizationRect @@ -1787,30 +2206,17 @@ {{319, 763}, {234, 73}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - {{85, 58}, {1084, 617}} + {{249, 66}, {1084, 617}} com.apple.InterfaceBuilder.CocoaPlugin - {{85, 58}, {1084, 617}} + {{249, 66}, {1084, 617}} {{117, 199}, {1084, 587}} - - YES - - YES - IBResizeActionFinalFrame - IBResizeActionInitialFrame - - - YES - {{60, 145}, {1084, 617}} - {{60, 175}, {1084, 587}} - - com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - {{313, 712}, {205, 113}} + {{313, 692}, {205, 133}} com.apple.InterfaceBuilder.CocoaPlugin {{319, 721}, {205, 113}} com.apple.InterfaceBuilder.CocoaPlugin @@ -1822,6 +2228,26 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + {{213, 731}, {773, 125}} + com.apple.InterfaceBuilder.CocoaPlugin + {{213, 731}, {773, 125}} + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -1846,6 +2272,7 @@ com.apple.InterfaceBuilder.CocoaPlugin + {{227, 622}, {199, 203}} com.apple.InterfaceBuilder.CocoaPlugin {{233, 633}, {199, 203}} @@ -1877,7 +2304,7 @@ - 404 + 433 @@ -1901,9 +2328,11 @@ YES YES + dismissSheet: enterFullScreen: redo: startGameFromMenu: + toggleRemoteOpponent: undo: @@ -1912,11 +2341,26 @@ id id id + id + id - _turnSlider - NSSlider + YES + + YES + _myMoveURLField + _opponentsMoveSheet + _opponentsMoveURLField + _turnSlider + + + YES + NSTextField + NSPanel + NSTextField + NSSlider + IBProjectSource diff -r a59acc683080 -r 6c78cc6bd7a6 English.lproj/MainMenu.nib/keyedobjects.nib Binary file English.lproj/MainMenu.nib/keyedobjects.nib has changed diff -r a59acc683080 -r 6c78cc6bd7a6 GeekGameBoard.xcodeproj/project.pbxproj --- a/GeekGameBoard.xcodeproj/project.pbxproj Thu May 29 15:04:06 2008 -0700 +++ b/GeekGameBoard.xcodeproj/project.pbxproj Thu Jul 03 17:44:30 2008 -0700 @@ -41,7 +41,6 @@ 27CCAABD0CB92A9F001CFE24 /* Card.m in Sources */ = {isa = PBXBuildFile; fileRef = 27CCAABC0CB92A9F001CFE24 /* Card.m */; }; 27CCABBF0CB9496B001CFE24 /* Bit.m in Sources */ = {isa = PBXBuildFile; fileRef = 27CCABBE0CB9496B001CFE24 /* Bit.m */; }; 27CCAC750CB95C2B001CFE24 /* PlayingCard.m in Sources */ = {isa = PBXBuildFile; fileRef = 27CCAC740CB95C2B001CFE24 /* PlayingCard.m */; }; - 27D014C00D8DFB4500615ADD /* Game-Persistence.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D014BF0D8DFB4500615ADD /* Game-Persistence.m */; }; 27D4F1260CCF011200923605 /* Stack.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D4F1250CCF011200923605 /* Stack.m */; }; 27DFC4410CCD01B7005E34CE /* GoGame.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DFC4400CCD01B7005E34CE /* GoGame.m */; }; 27F230B90CD1A61B006939C1 /* KlondikeGame.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F230B80CD1A61B006939C1 /* KlondikeGame.m */; }; @@ -110,8 +109,6 @@ 27CCABBE0CB9496B001CFE24 /* Bit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Bit.m; sourceTree = ""; }; 27CCAC730CB95C2B001CFE24 /* PlayingCard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlayingCard.h; sourceTree = ""; }; 27CCAC740CB95C2B001CFE24 /* PlayingCard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlayingCard.m; sourceTree = ""; }; - 27D014BE0D8DFB4500615ADD /* Game-Persistence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Game-Persistence.h"; sourceTree = ""; }; - 27D014BF0D8DFB4500615ADD /* Game-Persistence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Game-Persistence.m"; sourceTree = ""; }; 27D4F1240CCF011200923605 /* Stack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Stack.h; sourceTree = ""; }; 27D4F1250CCF011200923605 /* Stack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Stack.m; sourceTree = ""; }; 27DFC43F0CCD01B7005E34CE /* GoGame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoGame.h; sourceTree = ""; }; @@ -229,8 +226,6 @@ children = ( 27275C490CC700F2009C4C6C /* Game.h */, 27275C4A0CC700F2009C4C6C /* Game.m */, - 27D014BE0D8DFB4500615ADD /* Game-Persistence.h */, - 27D014BF0D8DFB4500615ADD /* Game-Persistence.m */, 27275C900CC7C578009C4C6C /* TicTacToeGame.h */, 27275C910CC7C578009C4C6C /* TicTacToeGame.m */, 2734B4EE0CCA5BDB0070C008 /* CheckersGame.h */, @@ -402,7 +397,6 @@ 27C999C30D81185E005AFD4F /* GGBUtils.m in Sources */, 279F4D870D8606C200B32DBF /* GGBLayer.m in Sources */, 279F4D880D8606C200B32DBF /* GGBTextLayer.m in Sources */, - 27D014C00D8DFB4500615ADD /* Game-Persistence.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff -r a59acc683080 -r 6c78cc6bd7a6 Resources/Gingko/Blue.png Binary file Resources/Gingko/Blue.png has changed diff -r a59acc683080 -r 6c78cc6bd7a6 Resources/Gingko/Gold.png Binary file Resources/Gingko/Gold.png has changed diff -r a59acc683080 -r 6c78cc6bd7a6 Resources/Gingko/Green.png Binary file Resources/Gingko/Green.png has changed diff -r a59acc683080 -r 6c78cc6bd7a6 Resources/Gingko/Red.png Binary file Resources/Gingko/Red.png has changed diff -r a59acc683080 -r 6c78cc6bd7a6 Resources/Gingko/Violet.png Binary file Resources/Gingko/Violet.png has changed diff -r a59acc683080 -r 6c78cc6bd7a6 Source/Bit.m --- a/Source/Bit.m Thu May 29 15:04:06 2008 -0700 +++ b/Source/Bit.m Thu Jul 03 17:44:30 2008 -0700 @@ -22,6 +22,7 @@ */ #import "Bit.h" #import "Game.h" +#import "Player.h" #import "QuartzUtils.h" diff -r a59acc683080 -r 6c78cc6bd7a6 Source/BitHolder.h --- a/Source/BitHolder.h Thu May 29 15:04:06 2008 -0700 +++ b/Source/BitHolder.h Thu Jul 03 17:44:30 2008 -0700 @@ -30,6 +30,9 @@ /** Current Bit, or nil if empty */ @property (retain) Bit* bit; +/** Sets current Bit to nil, triggering its "destroy" animation */ +- (void) destroyBit; + /** Conveniences for comparing self.bit with nil */ @property (readonly, getter=isEmpty) BOOL empty; diff -r a59acc683080 -r 6c78cc6bd7a6 Source/BitHolder.m --- a/Source/BitHolder.m Thu May 29 15:04:06 2008 -0700 +++ b/Source/BitHolder.m Thu Jul 03 17:44:30 2008 -0700 @@ -57,6 +57,14 @@ } } +- (void) destroyBit +{ + if( _bit ) { + [_bit destroy]; + setObj(&_bit,nil); + } +} + - (BOOL) isEmpty {return self.bit==nil;} @synthesize highlighted=_highlighted; diff -r a59acc683080 -r 6c78cc6bd7a6 Source/BoardUIView.m --- a/Source/BoardUIView.m Thu May 29 15:04:06 2008 -0700 +++ b/Source/BoardUIView.m Thu Jul 03 17:44:30 2008 -0700 @@ -68,7 +68,7 @@ [rootLayer addSublayer: _gameboard]; [_gameboard release]; - _game = [[gameClass alloc] initWithBoard: _gameboard]; + _game = [[gameClass alloc] initNewGameWithBoard: _gameboard]; } diff -r a59acc683080 -r 6c78cc6bd7a6 Source/BoardView.h --- a/Source/BoardView.h Thu May 29 15:04:06 2008 -0700 +++ b/Source/BoardView.h Thu Jul 03 17:44:30 2008 -0700 @@ -31,6 +31,7 @@ @private Game *_game; // Current Game GGBLayer *_gameboard; // Game's main layer + NSSize _oldSize; // Used during mouse-down tracking: NSPoint _dragStartPos; // Starting position of mouseDown @@ -48,13 +49,17 @@ NSDragOperation _viewDropOp; // Current drag operation } +@property (retain) Game *game; +@property (readonly) CALayer *gameboard; + - (void) startGameNamed: (NSString*)gameClassName; +- (void) createGameBoard; + +- (void) reverseBoard; + - (IBAction) enterFullScreen: (id)sender; -@property (readonly) Game *game; -@property (readonly) CALayer *gameboard; - - (CGRect) gameBoardFrame; @end diff -r a59acc683080 -r 6c78cc6bd7a6 Source/BoardView.m --- a/Source/BoardView.m Thu May 29 15:04:06 2008 -0700 +++ b/Source/BoardView.m Thu Jul 03 17:44:30 2008 -0700 @@ -24,6 +24,7 @@ #import "Bit.h" #import "BitHolder.h" #import "Game.h" +#import "Player.h" #import "QuartzUtils.h" #import "GGBUtils.h" @@ -36,7 +37,7 @@ @implementation BoardView -@synthesize game=_game, gameboard=_gameboard; +@synthesize gameboard=_gameboard; - (void) dealloc @@ -46,20 +47,63 @@ } +- (void) _removeGameBoard +{ + if( _gameboard ) { + RemoveImmediately(_gameboard); + _gameboard = nil; + } +} + +- (void) createGameBoard +{ + [self _removeGameBoard]; + _gameboard = [[CALayer alloc] init]; + _gameboard.frame = [self gameBoardFrame]; + _gameboard.autoresizingMask = kCALayerMinXMargin | kCALayerMaxXMargin | kCALayerMinYMargin | kCALayerMaxYMargin; + + // Tell the game to set up the board: + _game.board = _gameboard; + + [self.layer addSublayer: _gameboard]; + [_gameboard release]; +} + + +- (void) reverseBoard +{ + [_gameboard setValue: [NSNumber numberWithDouble: M_PI] + forKeyPath: @"transform.rotation"]; +} + + +- (Game*) game +{ + return _game; +} + +- (void) setGame: (Game*)game +{ + if( game!=_game ) { + setObj(&_game,game); + [self createGameBoard]; + } +} + - (void) startGameNamed: (NSString*)gameClassName { - if( _gameboard ) { - [_gameboard removeFromSuperlayer]; - _gameboard = nil; + Class gameClass = NSClassFromString(gameClassName); + Game *game = [[gameClass alloc] init]; + if( game ) { + self.game = game; + [game release]; } - _gameboard = [[CALayer alloc] init]; - _gameboard.frame = [self gameBoardFrame]; - _gameboard.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; - [self.layer addSublayer: _gameboard]; - [_gameboard release]; - - Class gameClass = NSClassFromString(gameClassName); - setObj(&_game, [[gameClass alloc] initWithBoard: _gameboard]); +} + + +- (BOOL) canMakeMove +{ + return (_game && _game.currentPlayer.local && _game.currentTurnNo==_game.maxTurnNo); } @@ -72,18 +116,46 @@ - (void)resetCursorRects { [super resetCursorRects]; - [self addCursorRect: self.bounds cursor: [NSCursor openHandCursor]]; + if( self.canMakeMove ) + [self addCursorRect: self.bounds cursor: [NSCursor openHandCursor]]; } - (IBAction) enterFullScreen: (id)sender { + [self _removeGameBoard]; if( self.isInFullScreenMode ) { [self exitFullScreenModeWithOptions: nil]; } else { [self enterFullScreenMode: self.window.screen withOptions: nil]; } + [self createGameBoard]; +} + + +- (void)viewWillStartLiveResize +{ + [super viewWillStartLiveResize]; + _oldSize = self.frame.size; +} + +- (void)setFrameSize:(NSSize)newSize +{ + [super setFrameSize: newSize]; + if( _oldSize.width > 0.0f ) { + CGAffineTransform xform = _gameboard.affineTransform; + xform.a = xform.d = MIN(newSize.width,newSize.height)/MIN(_oldSize.width,_oldSize.height); + _gameboard.affineTransform = xform; + } else + [self createGameBoard]; +} + +- (void)viewDidEndLiveResize +{ + [super viewDidEndLiveResize]; + _oldSize.width = _oldSize.height = 0.0f; + [self createGameBoard]; } @@ -149,6 +221,11 @@ - (void) mouseDown: (NSEvent*)ev { + if( ! self.canMakeMove ) { + NSBeep(); + return; + } + BOOL placing = NO; _dragStartPos = ev.locationInWindow; _dragBit = (Bit*) [self hitTestPoint: _dragStartPos diff -r a59acc683080 -r 6c78cc6bd7a6 Source/CheckersGame.h --- a/Source/CheckersGame.h Thu May 29 15:04:06 2008 -0700 +++ b/Source/CheckersGame.h Thu Jul 03 17:44:30 2008 -0700 @@ -20,7 +20,7 @@ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#import "Game.h" +#import "Game+Protected.h" @class Grid; diff -r a59acc683080 -r 6c78cc6bd7a6 Source/CheckersGame.m --- a/Source/CheckersGame.m Thu May 29 15:04:06 2008 -0700 +++ b/Source/CheckersGame.m Thu Jul 03 17:44:30 2008 -0700 @@ -30,6 +30,26 @@ @implementation CheckersGame +- (id) init +{ + self = [super init]; + if (self != nil) { + _cells = [[NSMutableArray alloc] init]; + [self setNumberOfPlayers: 2]; + + PreloadSound(@"Tink"); + PreloadSound(@"Funk"); + PreloadSound(@"Blow"); + PreloadSound(@"Pop"); + } + return self; +} + +- (CGImageRef) iconForPlayer: (int)playerNum +{ + return GetCGImageNamed( playerNum==0 ?@"Green.png" :@"Red.png" ); +} + - (Piece*) pieceForPlayer: (int)playerNum { Piece *p = [[Piece alloc] initWithImageNamed: (playerNum==0 ?@"Green.png" :@"Red.png") @@ -39,10 +59,18 @@ return [p autorelease]; } -- (Grid*) x_makeGrid +- (void) makeKing: (Piece*)piece +{ + piece.scale = 1.4; + [piece setValue: @"King" forKey: @"King"]; + piece.name = piece.owner.index ?@"4" :@"3"; +} + +- (void) setUpBoard { RectGrid *grid = [[[RectGrid alloc] initWithRows: 8 columns: 8 frame: _board.bounds] autorelease]; _grid = grid; + [_board addSublayer: _grid]; CGPoint pos = _grid.position; pos.x = floor((_board.bounds.size.width-grid.frame.size.width)/2); grid.position = pos; @@ -53,31 +81,11 @@ 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)]]; } - self.stateString = @"111111111111--------222222222222"; - return grid; -} - - -- (id) initWithBoard: (GGBLayer*)board -{ - self = [super initWithBoard: board]; - if (self != nil) { - [self setNumberOfPlayers: 2]; - _cells = [[NSMutableArray alloc] init]; - [self x_makeGrid]; - [board addSublayer: _grid]; - [self nextPlayer]; - - PreloadSound(@"Tink"); - PreloadSound(@"Funk"); - PreloadSound(@"Blow"); - PreloadSound(@"Pop"); - } - return self; } - (void) dealloc @@ -88,6 +96,11 @@ } +- (NSString*) initialStateString +{ + return @"111111111111--------222222222222"; +} + - (NSString*) stateString { unichar state[_cells.count]; @@ -108,11 +121,15 @@ int i = 0; for( GridCell *cell in _cells ) { Piece *piece; - switch( [state characterAtIndex: i++] ) { - case '1': piece = [self pieceForPlayer: 0]; _numPieces[0]++; break; - case '2': piece = [self pieceForPlayer: 1]; _numPieces[1]++; break; - default: piece = nil; break; - } + 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; } } @@ -134,9 +151,11 @@ Square *src=(Square*)srcHolder, *dst=(Square*)dstHolder; int playerIndex = self.currentPlayer.index; - if( self.currentMove.length==0 ) - [self.currentMove appendString: src.name]; - [self.currentMove appendString: dst.name]; + Turn *turn = self.currentTurn; + if( turn.move.length==0 ) + [turn addToMove: src.name]; + [turn addToMove: @"-"]; + [turn addToMove: dst.name]; BOOL isKing = ([bit valueForKey: @"King"] != nil); PlaySound(isKing ?@"Funk" :@"Tink"); @@ -145,8 +164,8 @@ if( dst.row == (playerIndex ?0 :7) ) if( ! isKing ) { PlaySound(@"Blow"); - bit.scale = 1.4; - [bit setValue: @"King" forKey: @"King"]; + [self makeKing: (Piece*)bit]; + [turn addToMove: @"*"]; // don't set isKing flag - piece can't jump again after being kinged. } @@ -163,9 +182,9 @@ if( capture ) { PlaySound(@"Pop"); - Bit *bit = capture.bit; - _numPieces[bit.owner.index]--; - [bit destroy]; + _numPieces[capture.bit.owner.index]--; + [capture destroyBit]; + [turn addToMove: @"!"]; // 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) ) @@ -192,16 +211,15 @@ - (BOOL) applyMoveString: (NSString*)move { - int length = move.length; - if( length<4 || (length&1) ) - return NO; GridCell *src = nil; - for( int i=0; i 0 ) - if( ! [self animateMoveFrom: src to: dst] ) - return NO; + if( dst == nil ) + return NO; + if( src && ! [self animateMoveFrom: src to: dst] ) + return NO; src = dst; } return YES; diff -r a59acc683080 -r 6c78cc6bd7a6 Source/DemoBoardView.h --- a/Source/DemoBoardView.h Thu May 29 15:04:06 2008 -0700 +++ b/Source/DemoBoardView.h Thu Jul 03 17:44:30 2008 -0700 @@ -29,11 +29,12 @@ { CATextLayer *_headline; IBOutlet NSSlider *_turnSlider; + IBOutlet NSPanel *_opponentsMoveSheet; + IBOutlet NSTextField *_myMoveURLField, *_opponentsMoveURLField; } - (IBAction) undo: (id)sender; - (IBAction) redo: (id)sender; - (IBAction) startGameFromMenu: (id)sender; -- (IBAction) enterFullScreen: (id)sender; @end diff -r a59acc683080 -r 6c78cc6bd7a6 Source/DemoBoardView.m --- a/Source/DemoBoardView.m Thu May 29 15:04:06 2008 -0700 +++ b/Source/DemoBoardView.m Thu Jul 03 17:44:30 2008 -0700 @@ -26,15 +26,6 @@ #import "QuartzUtils.h" -/** WARNING: THIS CODE REQUIRES GARBAGE COLLECTION! - ** This sample application uses Objective-C 2.0 garbage collection. - ** Therefore, the source code in this file does NOT perform manual object memory management. - ** If you reuse any of this code in a process that isn't garbage collected, you will need to - ** add all necessary retain/release/autorelease calls, and implement -dealloc methods, - ** otherwise unpleasant leakage will occur! - **/ - - @implementation DemoBoardView @@ -46,6 +37,14 @@ static NSString* sCurrentGameName = @"CheckersGame"; +- (IBAction) toggleRemoteOpponent: (id)sender +{ + NSAssert(self.game.currentTurn==0,@"Game has already begun"); + Player *opponent = [self.game.players objectAtIndex: 1]; + opponent.local = !opponent.local; +} + + - (void) startGameNamed: (NSString*)gameClassName { [super startGameNamed: gameClassName]; @@ -112,19 +111,26 @@ { Game *game = self.game; if( object == game ) { - NSLog(@"maxTurn = %u, currentTurn=%u", self.game.maxTurn,self.game.currentTurn); + NSLog(@"maxTurn = %u, currentTurn = %u", + self.game.maxTurn,self.game.currentTurn); + NSLog(@"Game state = '%@'", self.game.stateString); + _turnSlider.maxValue = self.game.maxTurn; _turnSlider.numberOfTickMarks = self.game.maxTurn+1; Player *p = game.winner; NSString *msg; if( p ) { + // The game is won [[NSSound soundNamed: @"Sosumi"] play]; - msg = @"%@ wins! Congratulations!"; + if( self.game.local ) + msg = @"%@ wins! Congratulations!"; + else + msg = p.local ?@"You Win! Congratulations!" :@"You Lose ... :-("; } else { + // Otherwise go on to the next turn: p = game.currentPlayer; msg = @"Your turn, %@"; - NSLog(@"Game state = '%@'", self.game.stateString); } _headline.string = [NSString stringWithFormat: msg, p.name]; } @@ -149,13 +155,6 @@ } -- (IBAction) enterFullScreen: (id)sender -{ - [super enterFullScreen: sender]; - [self startGameNamed: sCurrentGameName]; // restart game so it'll use the new size -} - - #pragma mark - #pragma mark NSAPPLICATION DELEGATE: diff -r a59acc683080 -r 6c78cc6bd7a6 Source/GGBLayer.h --- a/Source/GGBLayer.h Thu May 29 15:04:06 2008 -0700 +++ b/Source/GGBLayer.h Thu Jul 03 17:44:30 2008 -0700 @@ -47,3 +47,6 @@ /** Disables animations until EndDisableAnimations is called. */ void BeginDisableAnimations(void); void EndDisableAnimations(void); + +CGColorRef GetEffectiveBackground( CALayer *layer ); + diff -r a59acc683080 -r 6c78cc6bd7a6 Source/GGBLayer.m --- a/Source/GGBLayer.m Thu May 29 15:04:06 2008 -0700 +++ b/Source/GGBLayer.m Thu Jul 03 17:44:30 2008 -0700 @@ -288,3 +288,12 @@ } +CGColorRef GetEffectiveBackground( CALayer *layer ) +{ + for( ; layer; layer=layer.superlayer ) { + CGColorRef bg = layer.backgroundColor; + if( bg ) + return bg; + } + return nil; +} diff -r a59acc683080 -r 6c78cc6bd7a6 Source/GGBTextLayer.m --- a/Source/GGBTextLayer.m Thu May 29 15:04:06 2008 -0700 +++ b/Source/GGBTextLayer.m Thu Jul 03 17:44:30 2008 -0700 @@ -51,13 +51,17 @@ label.foregroundColor = kBlackColor; NSString *mode; - if( align & kCALayerWidthSizable ) + if( (align & (kCALayerMinXMargin | kCALayerMaxXMargin)) == (kCALayerMinXMargin | kCALayerMaxXMargin) ) mode = @"center"; - else if( align & kCALayerMinXMargin ) - mode = @"right"; - else - mode = @"left"; - align |= kCALayerWidthSizable; + else { + if( align & kCALayerWidthSizable ) + mode = @"center"; + else if( align & kCALayerMinXMargin ) + mode = @"right"; + else + mode = @"left"; + align |= kCALayerWidthSizable; + } label.alignmentMode = mode; // Get the bounds of the interior of the superlayer: diff -r a59acc683080 -r 6c78cc6bd7a6 Source/GGBUtils.h --- a/Source/GGBUtils.h Thu May 29 15:04:06 2008 -0700 +++ b/Source/GGBUtils.h Thu Jul 03 17:44:30 2008 -0700 @@ -29,6 +29,7 @@ /** Just like setObj except that it _copies_ the new value. */ void setObjCopy( id *variable, id newValue ); +void DelayFor( NSTimeInterval interval ); void PreloadSound( NSString* name ); void PlaySound( NSString* name ); diff -r a59acc683080 -r 6c78cc6bd7a6 Source/GGBUtils.m --- a/Source/GGBUtils.m Thu May 29 15:04:06 2008 -0700 +++ b/Source/GGBUtils.m Thu Jul 03 17:44:30 2008 -0700 @@ -42,6 +42,16 @@ } +void DelayFor( NSTimeInterval interval ) +{ + NSDate *end = [NSDate dateWithTimeIntervalSinceNow: interval]; + while( [end timeIntervalSinceNow] > 0 ) { + if( ! [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: end] ) + break; + } +} + + #if TARGET_OS_IPHONE static SystemSoundID GetSound( NSString *name ) { diff -r a59acc683080 -r 6c78cc6bd7a6 Source/Game+Protected.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Source/Game+Protected.h Thu Jul 03 17:44:30 2008 -0700 @@ -0,0 +1,65 @@ +// +// Game+Protected.h +// YourMove +// +// Created by Jens Alfke on 7/3/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + + +#import "Game.h" +#import "Player.h" +#import "Turn.h" +#import "Bit.h" +#import "BitHolder.h" + + +/** Game API for subclasses to use / override */ +@interface Game (Protected) + +/** Should return a string describing the initial state of a new game. + The default value is an empty string. */ +- (NSString*) initialStateString; + + +#pragma mark Abstract methods for subclasses to implement: + +/** Called by -setBoard: Should all all necessary Grids/Pieces/Cards/etc. to _board. + This method is always called during initialization of a new Game, and may be called + again afterwards, for example if the board area is resized. */ +- (void) setUpBoard; + +/** Should return the winning player, if the current position is a win, else nil. + Default implementation returns nil. */ +- (Player*) checkForWinner; + + +#pragma mark Protected methods for subclasses to call: + +/** Sets the number of players in the game. Subclass initializers should call this. */ +- (void) setNumberOfPlayers: (unsigned)n; + +/** Animate a piece moving from src to dst. Used in implementing -applyMoveString:. */ +- (BOOL) animateMoveFrom: (CALayer*)src to: (CALayer*)dst; + +/** Animate a piece being placed in dst. Used in implementing -applyMoveString:. */ +- (BOOL) animatePlacementIn: (CALayer*)dst; + +/** Checks for a winner and advances to the next player. */ +- (void) endTurn; + +@end + + +/** Optional Game API for tracking the history of a game, and being able to replay moves. */ +@interface Game (State) + +/** A string describing the current state of the game (the positions of all pieces, + orderings of cards, player scores, ... */ +@property (copy) NSString* stateString; + +/** Add a move to the game based on the contents of the string. + The string must have been returned by -currentMove at some point. */ +- (BOOL) applyMoveString: (NSString*)move; + +@end diff -r a59acc683080 -r 6c78cc6bd7a6 Source/Game-Persistence.h --- a/Source/Game-Persistence.h Thu May 29 15:04:06 2008 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -// -// Game-Persistence.h -// GeekGameBoard -// -// Created by Jens Alfke on 3/16/08. -// Copyright 2008 __MyCompanyName__. All rights reserved. -// - -#import "Game.h" - - -@interface Game (Persistence) - -+ (Game*) gameWithURL: (NSURL*)url; - -- (NSURL*) asURL; - -- (BOOL) addMoveFromURL: (NSURL*)url; - -@end diff -r a59acc683080 -r 6c78cc6bd7a6 Source/Game-Persistence.m --- a/Source/Game-Persistence.m Thu May 29 15:04:06 2008 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -// -// Game-Persistence.m -// GeekGameBoard -// -// Created by Jens Alfke on 3/16/08. -// Copyright 2008 __MyCompanyName__. All rights reserved. -// - -#import "Game-Persistence.h" - - -static NSDictionary* parseURLFields( NSURL* url ); - - -@implementation Game (Persistence) - - -static NSMutableDictionary *sPersistentGames; - - -- (id) initWithCoder: (NSCoder*)decoder -{ - self = [self init]; - if( self ) { - _players = [[decoder decodeObjectForKey: @"players"] mutableCopy]; - _states = [[decoder decodeObjectForKey: @"states"] mutableCopy]; - _moves = [[decoder decodeObjectForKey: @"moves"] mutableCopy]; - self.currentTurn = self.maxTurn; - } - return self; -} - - -- (void) encodeWithCoder: (NSCoder*)coder -{ - [coder encodeObject: _players forKey: @"players"]; - [coder encodeObject: _states forKey: @"states"]; - [coder encodeObject: _moves forKey: @"moves"]; -} - - -- (NSURL*) asURL -{ - return [NSURL URLWithString: - [NSString stringWithFormat: @"game:type=%@&id=%@&turn=%u&move=%@", - [[self class] identifier], _uniqueID, self.currentTurn,_moves.lastObject]]; -} - - -+ (Game*) gameWithURL: (NSURL*)url -{ - if( 0 != [@"game" caseInsensitiveCompare: url.scheme] ) - return nil; - NSDictionary *fields = parseURLFields(url); - NSString *type = [fields objectForKey: @"type"]; - NSString *uuid = [fields objectForKey: @"id"]; - int turn = [[fields objectForKey: @"turn"] intValue]; - if( !type || !uuid || turn<=0 ) - return nil; - - Game *game = [sPersistentGames objectForKey: uuid]; - if( game ) { - if( ![type isEqualToString: [[game class] identifier]] ) - return nil; - } else if( turn == 1 ) { - Class gameClass = NSClassFromString( [type stringByAppendingString: @"Game"] ); - if( ! gameClass || ! [gameClass isSubclassOfClass: [Game class]] ) - return nil; - game = [[gameClass alloc] initWithUniqueID: uuid]; - [game setNumberOfPlayers: 2]; - if( ! sPersistentGames ) - sPersistentGames = [[NSMutableDictionary alloc] init]; - [sPersistentGames setObject: game forKey: uuid]; - [game release]; - } - return game; -} - - -- (BOOL) addMoveFromURL: (NSURL*)url -{ - NSDictionary *fields = parseURLFields(url); - NSString *uuid = [fields objectForKey: @"id"]; - NSString *move = [fields objectForKey: @"move"]; - int turn = [[fields objectForKey: @"turn"] intValue]; - return [uuid isEqualToString: self.uniqueID] - && turn==self.currentTurn - && move.length > 0 - && [self applyMoveString: move]; -} - - - -@end - - - -static NSDictionary* parseURLFields( NSURL* url ) -{ - // Parse the URL into key-value pairs: - NSMutableDictionary *fields = [NSMutableDictionary dictionary]; - for( NSString *field in [url.resourceSpecifier componentsSeparatedByString: @"&"] ) { - NSRange e = [field rangeOfString: @"="]; - NSString *key, *value; - if( e.length>0 ) { - key = [field substringToIndex: e.location]; - value = [field substringFromIndex: NSMaxRange(e)]; - } else { - key= field; - value = @""; - } - [fields setObject: value forKey: key]; - } - return fields; -} diff -r a59acc683080 -r 6c78cc6bd7a6 Source/Game.h --- a/Source/Game.h Thu May 29 15:04:06 2008 -0700 +++ b/Source/Game.h Thu Jul 03 17:44:30 2008 -0700 @@ -20,56 +20,72 @@ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -@class GGBLayer, Bit, BitHolder, Player; + +#import +@class GGBLayer, Bit, BitHolder, Player, Turn; @protocol BitHolder; /** Abstract superclass. Keeps track of the rules and turns of a game. */ -@interface Game : NSObject +@interface Game : NSObject { - NSString *_uniqueID; GGBLayer *_board; NSArray *_players; - Player *_currentPlayer, *_winner; - NSMutableString *_currentMove; - NSMutableArray *_states, *_moves; - unsigned _currentTurn; + Player *_winner; + NSMutableArray *_turns; + unsigned _currentTurnNo; + NSMutableDictionary *_extraValues; + BOOL _requireConfirmation; } -/** Returns the name used to identify this game in URLs. - (By default it just returns the class name with the "Game" suffix removed.) */ +#pragma mark Class properties: + +/** The name used to identify this class of game in URLs. + (By default it just returns the class name with the "Game" suffix removed.) */ + (NSString*) identifier; -/** Returns the human-readable name of this game. +/** The human-readable name of this class of game. (By default it just returns the class name with the "Game" suffix removed.) */ + (NSString*) displayName; +/** Is this game's board wider than it's high? */ + (BOOL) landscapeOriented; + +/** Designated initializer: override this if your subclass needs additional initialization. */ +- (id) init; + +/** Convenience initializer that calls -init, -setBoard:, and -nextTurn. */ +- (id) initNewGameWithBoard: (GGBLayer*)board; + +/** NSCoding initializer. Calls -init, but then restores saved payers, states, moves. */ +- (id) initWithCoder: (NSCoder*)decoder; + +/** NSCoding method to save Game to an archive. */ +- (void) encodeWithCoder: (NSCoder*)coder; + + @property (readonly, copy) NSArray *players; -@property (readonly) Player *currentPlayer, *winner; +@property (readonly) Player *currentPlayer, *winner, *remotePlayer; +@property (readonly, getter=isLocal) BOOL local; // Are all players local? -@property (readonly) NSArray *states, *moves; -@property (readonly) unsigned maxTurn; -@property unsigned currentTurn; +@property (retain) GGBLayer *board; // The root layer for the game. + +@property (readonly) NSArray *turns; +@property (readonly) Turn *currentTurn, *latestTurn; +@property (readonly) unsigned maxTurnNo; +@property unsigned currentTurnNo; @property (readonly) BOOL isLatestTurn; +@property BOOL requireConfirmation; +- (void) cancelCurrentTurn; +- (void) confirmCurrentTurn; -/** A globally-unique string assigned to this game instance, to help networked players identify it. */ -@property (readonly) NSString* uniqueID; -- (BOOL) animateMoveFrom: (BitHolder*)src to: (BitHolder*)dst; +#pragma mark Methods for subclasses to implement: - -- (id) initWithUniqueID: (NSString*)uniqueID; -- (id) init; - - -// Methods for subclasses to implement: - -/** Designated initializer. After calling the superclass implementation, - it should add the necessary Grids, Pieces, Cards, Decks etc. to the board. */ -- (id) initWithBoard: (GGBLayer*)board; +/** An icon for a player (usually the same as the image of the player's pieces.) */ +- (CGImageRef) iconForPlayer: (int)playerIndex; /** Should return YES if it is legal for the given bit to be moved from its current holder. @@ -96,60 +112,4 @@ Default implementation always returns YES. */ - (BOOL) clickedBit: (Bit*)bit; -/** Should return the winning player, if the current position is a win, else nil. - Default implementation returns nil. */ -- (Player*) checkForWinner; - -/** A string describing the current state of the game (the positions of all pieces, - orderings of cards, player scores, ... */ -@property (copy) NSString* stateString; - -/** Add a move to the game based on the contents of the string. */ -- (BOOL) applyMoveString: (NSString*)move; - - -// Protected methods for subclasses to call: - -/** Sets the number of players in the game. Subclass initializers should call this. */ -- (void) setNumberOfPlayers: (unsigned)n; - -/** The current move in progress. Append text to it as the user makes moves. */ -@property (readonly) NSMutableString* currentMove; - -/** Advance to the next player, when a turn is over. */ -- (void) nextPlayer; - -/** Checks for a winner and advances to the next player. */ -- (void) endTurn; - @end - - - -/** A mostly-passive object used to represent a player. */ -@interface Player : NSObject -{ - Game *_game; - NSString *_name; -} - -- (id) initWithGame: (Game*)game; - -@property (readonly) Game *game; -@property (copy) NSString *name; -@property (readonly) int index; -@property (readonly, getter=isCurrent) BOOL current; -@property (readonly, getter=isFriendly) BOOL friendly; -@property (readonly, getter=isUnfriendly) BOOL unfriendly; -@property (readonly) Player *nextPlayer, *previousPlayer; - -@end - - - -@interface CALayer (Game) - -/** Called on any CALayer in the game's layer tree, will return the current Game object. */ -@property (readonly) Game *game; - -@end diff -r a59acc683080 -r 6c78cc6bd7a6 Source/Game.m --- a/Source/Game.m Thu May 29 15:04:06 2008 -0700 +++ b/Source/Game.m Thu Jul 03 17:44:30 2008 -0700 @@ -20,71 +20,60 @@ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#import "Game.h" -#import "Bit.h" -#import "BitHolder.h" +#import "Game+Protected.h" #import "QuartzUtils.h" +#import "GGBUtils.h" @interface Game () @property (copy) NSArray *players; -@property (assign) Player *currentPlayer, *winner; +@property (assign) Player *winner; +- (void) _startTurn; @end @implementation Game -+ (NSString*) identifier +- (id) init { - NSString* name = [self description]; - if( [name hasSuffix: @"Game"] ) - name = [name substringToIndex: name.length-4]; - return name; -} - - -+ (NSString*) displayName -{ - return [self identifier]; -} - - -- (id) initWithUniqueID: (NSString*)uuid -{ - NSParameterAssert(uuid); self = [super init]; if (self != nil) { - _uniqueID = [uuid copy]; - _board = [[GGBLayer alloc] init]; - // Store a pointer to myself as the value of the "Game" property - // of my root layer. (CALayers can have arbitrary KV properties stored into them.) - // This is used by the -[CALayer game] category method defined below, to find the Game. - [_board setValue: self forKey: @"Game"]; - - _currentMove = [[NSMutableString alloc] init]; + // Don't create _turns till -initWithCoder or -setNumberOfPlayers:. } return self; } -- (id) init + +- (id) initWithCoder: (NSCoder*)decoder { - CFUUIDRef uuidRef = CFUUIDCreate(NULL); - NSString* uuid = (id) CFUUIDCreateString(NULL,uuidRef); - self = [self initWithUniqueID: uuid]; - CFRelease(uuid); - CFRelease(uuidRef); + self = [self init]; + if( self ) { + _players = [[decoder decodeObjectForKey: @"players"] mutableCopy]; + _winner = [decoder decodeObjectForKey: @"winner"]; + _turns = [[decoder decodeObjectForKey: @"turns"] mutableCopy]; + _extraValues = [[decoder decodeObjectForKey: @"extraValues"] mutableCopy]; + self.currentTurnNo = self.maxTurnNo; + } return self; } -- (id) initWithBoard: (GGBLayer*)board + +- (void) encodeWithCoder: (NSCoder*)coder +{ + [coder encodeObject: _players forKey: @"players"]; + [coder encodeObject: _winner forKey: @"winner"]; + [coder encodeObject: _turns forKey: @"turns"]; + [coder encodeObject: _extraValues forKey: @"extraValues"]; +} + + +- (id) initNewGameWithBoard: (GGBLayer*)board { self = [self init]; - if (self != nil) { - _states = [[NSMutableArray alloc] init]; - _moves = [[NSMutableArray alloc] init]; - _board = [board retain]; - [board setValue: self forKey: @"Game"]; + if( self ) { + self.board = board; + NSAssert1(_players && _turns, @"%@ failed to set numberOfPlayers",self); } return self; } @@ -94,15 +83,67 @@ { [_board release]; [_players release]; - [_currentMove release]; - [_states release]; - [_moves release]; + [_turns release]; + [_extraValues release]; [super dealloc]; } -@synthesize players=_players, currentPlayer=_currentPlayer, winner=_winner, - currentMove=_currentMove, states=_states, moves=_moves, uniqueID=_uniqueID; +@synthesize players=_players, winner=_winner, turns=_turns, requireConfirmation=_requireConfirmation; + + +- (id)valueForUndefinedKey:(NSString *)key +{ + return [_extraValues objectForKey: key]; +} + +- (void)setValue:(id)value forUndefinedKey:(NSString *)key +{ + if( ! _extraValues ) + _extraValues = [[NSMutableDictionary alloc] init]; + if( value ) + [_extraValues setObject: value forKey: key]; + else + [_extraValues removeObjectForKey: key]; +} + + +#pragma mark - +#pragma mark BOARD: + + +- (void) setUpBoard +{ + NSAssert1(NO,@"%@ forgot to implement -setUpBoard",[self class]); +} + +- (GGBLayer*) board +{ + return _board; +} + +- (void) setBoard: (GGBLayer*)board +{ + setObj(&_board,board); + // Store a pointer to myself as the value of the "Game" property + // of my root layer. (CALayers can have arbitrary KV properties stored into them.) + // This is used by the -[CALayer game] category method defined below, to find the Game. + [_board setValue: self forKey: @"Game"]; + + BeginDisableAnimations(); + + // Tell the game to add the necessary bits to the board: + [self setUpBoard]; + + // Re-apply the current state to set up the pieces/cards: + self.stateString = [[_turns objectAtIndex: _currentTurnNo] boardState]; + + EndDisableAnimations(); +} + + +#pragma mark - +#pragma mark PLAYERS: - (void) setNumberOfPlayers: (unsigned)n @@ -114,105 +155,184 @@ [players addObject: player]; [player release]; } + self.players = players; self.winner = nil; - self.currentPlayer = nil; - self.players = players; + + Turn *turn = [[Turn alloc] initStartOfGame: self]; + setObj(&_turns, [NSMutableArray arrayWithObject: turn]); + [turn release]; + [self _startTurn]; } - -- (void) addToMove: (NSString*)str; +- (Player*) remotePlayer { - [_currentMove appendString: str]; + for( Player *player in _players ) + if( ! player.local ) + return player; + return nil; } - -- (BOOL) _rememberState +- (BOOL) isLocal { - if( self.isLatestTurn ) { - [_states addObject: self.stateString]; - return YES; - } else - return NO; + return self.remotePlayer == nil; } +- (Player*) currentPlayer +{ + return self.currentTurn.player; +} -- (void) nextPlayer ++ (NSArray*) keyPathsForValuesAffectingCurrentPlayer {return [NSArray arrayWithObject: @"currentTurn"];} + + +#pragma mark - +#pragma mark TURNS: + + +- (Turn*) currentTurn { - BOOL latestTurn = [self _rememberState]; - if( ! _currentPlayer ) { - NSLog(@"*** The %@ Begins! ***", self.class); - self.currentPlayer = [_players objectAtIndex: 0]; - } else { - self.currentPlayer = _currentPlayer.nextPlayer; - if( latestTurn ) { - [self willChangeValueForKey: @"currentTurn"]; - _currentTurn++; - [self didChangeValueForKey: @"currentTurn"]; - } - } - NSLog(@"Current player is %@",_currentPlayer); + return [_turns objectAtIndex: _currentTurnNo]; +} + +- (Turn*) latestTurn +{ + return [_turns lastObject]; +} + ++ (NSArray*) keyPathsForValuesAffectingCurrentTurn {return [NSArray arrayWithObject: @"currentTurnNo"];} ++ (NSArray*) keyPathsForValuesAffectingLatestTurn {return [NSArray arrayWithObject: @"turns"];} + + +- (void) _startTurn +{ + Turn *lastTurn = [_turns lastObject]; + NSAssert(lastTurn.status==kTurnFinished,@"Can't _startTurn till previous turn is finished"); + Turn *newTurn = [[Turn alloc] initWithPlayer: lastTurn.nextPlayer]; + + [self willChangeValueForKey: @"turns"]; + [_turns addObject: newTurn]; + [self willChangeValueForKey: @"turns"]; + [newTurn release]; + self.currentTurnNo = _turns.count-1; } - (void) endTurn { - NSLog(@"--- End of turn (move was '%@')", _currentMove); - if( self.isLatestTurn ) { - NSString *move = [[_currentMove copy] autorelease]; - [_currentMove setString: @""]; - [self willChangeValueForKey: @"maxTurn"]; - [_moves addObject: move]; - [self didChangeValueForKey: @"maxTurn"]; - } + Turn *curTurn = self.currentTurn; + if( curTurn.isLatestTurn && ! curTurn.replaying ) { + curTurn.status = kTurnComplete; + NSLog(@"--- End of %@", curTurn); + + Player *winner = [self checkForWinner]; + if( winner ) { + NSLog(@"*** The %@ Ends! The winner is %@ ! ***", self.class, winner); + self.winner = winner; + } + + if( ! _requireConfirmation || !curTurn.player.local ) + [self confirmCurrentTurn]; - Player *winner = [self checkForWinner]; - if( winner ) { - NSLog(@"*** The %@ Ends! The winner is %@ ! ***", self.class, winner); - [self _rememberState]; - self.winner = winner; - } else - [self nextPlayer]; -} - - -#pragma mark - -#pragma mark STORED TURNS: - - -- (unsigned) maxTurn -{ - return _moves.count; -} - -- (unsigned) currentTurn -{ - return _currentTurn; -} - -- (void) setCurrentTurn: (unsigned)turn -{ - NSParameterAssert(turn<=self.maxTurn); - if( turn != _currentTurn ) { - if( turn==_currentTurn+1 ) { - [self applyMoveString: [_moves objectAtIndex: _currentTurn]]; - } else { - BeginDisableAnimations(); - self.stateString = [_states objectAtIndex: turn]; - EndDisableAnimations(); - } - _currentTurn = turn; - self.currentPlayer = [_players objectAtIndex: (turn % _players.count)]; + [[NSNotificationCenter defaultCenter] postNotificationName: kTurnCompleteNotification + object: curTurn]; } } +- (void) cancelCurrentTurn +{ + Turn *curTurn = self.currentTurn; + if( curTurn.status > kTurnEmpty && curTurn.status < kTurnFinished ) { + if( _winner ) + self.winner = nil; + if( _board ) + self.stateString = curTurn.previousTurn.boardState; + curTurn.status = kTurnEmpty; + } +} + +- (void) confirmCurrentTurn +{ + Turn *curTurn = self.currentTurn; + if( curTurn.status == kTurnComplete ) { + curTurn.status = kTurnFinished; + if( ! _winner ) + [self _startTurn]; + } +} + - (BOOL) isLatestTurn { - return _currentTurn == MAX(_states.count,1)-1; + return _currentTurnNo == _turns.count-1; } +- (unsigned) maxTurnNo +{ + return _turns.count-1; +} -- (BOOL) animateMoveFrom: (BitHolder*)src to: (BitHolder*)dst ++ (NSArray*) keyPathsForValuesAffectingIsLatestTurn {return [NSArray arrayWithObjects: @"currentTurnNo",@"turns",nil];} ++ (NSArray*) keyPathsForValuesAffectingMaxTurnNo {return [NSArray arrayWithObjects: @"turns",nil];} + +- (unsigned) currentTurnNo +{ + return _currentTurnNo; +} + + +#pragma mark - +#pragma mark REPLAYING TURNS: + + +- (void) setCurrentTurnNo: (unsigned)turnNo +{ + NSParameterAssert(turnNo<=self.maxTurnNo); + unsigned oldTurnNo = _currentTurnNo; + if( turnNo != oldTurnNo ) { + if( _board ) { + Turn *turn = [_turns objectAtIndex: turnNo]; + NSString *state; + if( turn.status == kTurnEmpty ) + state = turn.previousTurn.boardState; + else + state = turn.boardState; + NSAssert1(state,@"empty boardState at turn #%i",turnNo); + _currentTurnNo = turnNo; + if( turnNo==oldTurnNo+1 ) { + NSString *move = turn.move; + if( move ) { + NSLog(@"Reapplying move '%@'",move); + turn.replaying = YES; + @try{ + if( ! [self applyMoveString: move] ) { + _currentTurnNo = oldTurnNo; + NSBeep(); + NSLog(@"WARNING: %@ failed to apply stored move '%@'!", self,move); + return; + } + }@finally{ + turn.replaying = NO; + } + } + } else { + NSLog(@"Reapplying state '%@'",state); + BeginDisableAnimations(); + self.stateString = state; + EndDisableAnimations(); + } + if( ! [self.stateString isEqual: state] ) { + _currentTurnNo = oldTurnNo; + NSBeep(); + NSLog(@"WARNING: %@ failed to apply stored state '%@'!", self,state); + return; + } + } else + _currentTurnNo = turnNo; + } +} + + +- (BOOL) animateMoveFrom: (CALayer*)src to: (CALayer*)dst { if( src==nil || dst==nil || dst==src ) return NO; @@ -241,7 +361,35 @@ [src draggedBit: bit to: dst]; [self bit: bit movedFrom: src to: dst]; - src = dst; + return YES; +} + + +- (BOOL) animatePlacementIn: (CALayer*)dst +{ + if( dst == nil ) + return NO; + Bit *bit = [self bitToPlaceInHolder: dst]; + if( ! bit ) + return NO; + + CALayer* oldHolder = (CALayer*) bit.holder; + if( oldHolder ) { + if( oldHolder != dst ) + return [self animateMoveFrom: oldHolder to: dst]; + } else + bit.position = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: _board.superlayer]; + ChangeSuperlayer(bit, _board.superlayer, -1); + bit.pickedUp = YES; + dst.highlighted = YES; + + DelayFor(0.2); + + dst.bit = bit; + dst.highlighted = NO; + bit.pickedUp = NO; + + [self bit: bit movedFrom: nil to: dst]; return YES; } @@ -250,12 +398,37 @@ #pragma mark GAMEPLAY METHODS TO BE OVERRIDDEN: ++ (NSString*) identifier +{ + NSString* name = [self description]; + if( [name hasSuffix: @"Game"] ) + name = [name substringToIndex: name.length-4]; + return name; +} + ++ (NSString*) displayName +{ + return [self identifier]; +} + + (BOOL) landscapeOriented { return NO; } +- (NSString*) initialStateString +{ + return @""; +} + + +- (CGImageRef) iconForPlayer: (int)playerIndex +{ + return nil; +} + + - (BOOL) canBit: (Bit*)bit moveFrom: (id)src { return YES; @@ -287,83 +460,20 @@ return nil; } - +/* These are abstract + - (NSString*) stateString {return @"";} - (void) setStateString: (NSString*)s { } - (BOOL) applyMoveString: (NSString*)move {return NO;} +*/ @end -@implementation Player - - -- (id) initWithGame: (Game*)game -{ - self = [super init]; - if (self != nil) { - _game = game; - } - return self; -} - -- (id) initWithCoder: (NSCoder*)decoder -{ - self = [self init]; - if( self ) { - _game = [decoder decodeObjectForKey: @"game"]; - _name = [[decoder decodeObjectForKey: @"name"] copy]; - } - return self; -} - -- (void) encodeWithCoder: (NSCoder*)coder -{ - [coder encodeObject: _game forKey: @"game"]; - [coder encodeObject: _name forKey: @"name"]; -} - -- (void) dealloc -{ - [_name release]; - [super dealloc]; -} - - -@synthesize game=_game, name=_name; - -- (BOOL) isCurrent {return self == _game.currentPlayer;} -- (BOOL) isFriendly {return self == _game.currentPlayer;} // could be overridden for games with partners -- (BOOL) isUnfriendly {return ! self.friendly;} - -- (int) index -{ - return [_game.players indexOfObjectIdenticalTo: self]; -} - -- (Player*) nextPlayer -{ - return [_game.players objectAtIndex: (self.index+1) % _game.players.count]; -} - -- (Player*) previousPlayer -{ - return [_game.players objectAtIndex: (self.index-1) % _game.players.count]; -} - -- (NSString*) description -{ - return [NSString stringWithFormat: @"%@[%@]", self.class,self.name]; -} - -@end - - - - +#pragma mark - @implementation CALayer (Game) - (Game*) game diff -r a59acc683080 -r 6c78cc6bd7a6 Source/GoGame.h --- a/Source/GoGame.h Thu May 29 15:04:06 2008 -0700 +++ b/Source/GoGame.h Thu Jul 03 17:44:30 2008 -0700 @@ -20,7 +20,7 @@ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#import "Game.h" +#import "Game+Protected.h" @class RectGrid, Stack; @@ -33,3 +33,13 @@ } @end + + +/** 9x9 Go */ +@interface Go9Game : GoGame +@end + + +/** 13x13 Go */ +@interface Go13Game : GoGame +@end diff -r a59acc683080 -r 6c78cc6bd7a6 Source/GoGame.m --- a/Source/GoGame.m Thu May 29 15:04:06 2008 -0700 +++ b/Source/GoGame.m Thu Jul 03 17:44:30 2008 -0700 @@ -32,76 +32,93 @@ @implementation GoGame -- (id) initWithBoard: (GGBLayer*)board ++ (int) dimensions {return 19;} + +- (id) init { - self = [super initWithBoard: board]; + self = [super init]; if (self != nil) { [self setNumberOfPlayers: 2]; [(Player*)[_players objectAtIndex: 0] setName: @"Red"]; [(Player*)[_players objectAtIndex: 1] setName: @"White"]; - - CGSize size = board.bounds.size; - CGFloat boardSide = MIN(size.width,size.height); - RectGrid *grid = [[RectGrid alloc] initWithRows: 9 columns: 9 - frame: CGRectMake(floor((size.width-boardSide)/2), - floor((size.height-boardSide)/2), - boardSide,boardSide)]; - _grid = grid; - grid.backgroundColor = GetCGPatternNamed(@"Wood.jpg"); - grid.borderColor = kTranslucentLightGrayColor; - grid.borderWidth = 2; - grid.lineColor = kTranslucentGrayColor; - grid.cellClass = [GoSquare class]; - [grid addAllCells]; - ((GoSquare*)[grid cellAtRow: 2 column: 2]).dotted = YES; - ((GoSquare*)[grid cellAtRow: 6 column: 6]).dotted = YES; - ((GoSquare*)[grid cellAtRow: 2 column: 6]).dotted = YES; - ((GoSquare*)[grid cellAtRow: 6 column: 2]).dotted = YES; - grid.usesDiagonals = grid.allowsMoves = grid.allowsCaptures = NO; - [board addSublayer: grid]; - [grid release]; - - CGRect gridFrame = grid.frame; - CGFloat pieceSize = (int)grid.spacing.width & ~1; // make sure it's even - CGFloat captureHeight = gridFrame.size.height-4*pieceSize; - _captured[0] = [[Stack alloc] initWithStartPos: CGPointMake(2*pieceSize,0) - spacing: CGSizeMake(0,pieceSize) - wrapInterval: floor(captureHeight/pieceSize) - wrapSpacing: CGSizeMake(-pieceSize,0)]; - _captured[0].frame = CGRectMake(CGRectGetMinX(gridFrame)-3*pieceSize, - CGRectGetMinY(gridFrame)+3*pieceSize, - 2*pieceSize, captureHeight); - _captured[0].zPosition = kPieceZ+1; - [board addSublayer: _captured[0]]; - [_captured[0] release]; - - _captured[1] = [[Stack alloc] initWithStartPos: CGPointMake(0,captureHeight) - spacing: CGSizeMake(0,-pieceSize) - wrapInterval: floor(captureHeight/pieceSize) - wrapSpacing: CGSizeMake(pieceSize,0)]; - _captured[1].frame = CGRectMake(CGRectGetMaxX(gridFrame)+pieceSize, - CGRectGetMinY(gridFrame)+pieceSize, - 2*pieceSize, captureHeight); - _captured[1].zPosition = kPieceZ+1; - [board addSublayer: _captured[1]]; - [_captured[1] release]; - - [self nextPlayer]; - PreloadSound(@"Pop"); -} + } return self; } + +- (void) setUpBoard +{ + int dimensions = [[self class] dimensions]; + CGSize size = _board.bounds.size; + CGFloat boardSide = MIN(size.width,size.height); + RectGrid *grid = [[RectGrid alloc] initWithRows: dimensions columns: dimensions + frame: CGRectMake(floor((size.width-boardSide)/2), + floor((size.height-boardSide)/2), + boardSide,boardSide)]; + _grid = grid; + /* + grid.backgroundColor = GetCGPatternNamed(@"Wood.jpg"); + grid.borderColor = kTranslucentLightGrayColor; + grid.borderWidth = 2; + */ + grid.lineColor = kTranslucentGrayColor; + grid.cellClass = [GoSquare class]; + [grid addAllCells]; + ((GoSquare*)[grid cellAtRow: 2 column: 2]).dotted = YES; + ((GoSquare*)[grid cellAtRow: 6 column: 6]).dotted = YES; + ((GoSquare*)[grid cellAtRow: 2 column: 6]).dotted = YES; + ((GoSquare*)[grid cellAtRow: 6 column: 2]).dotted = YES; + grid.usesDiagonals = grid.allowsMoves = grid.allowsCaptures = NO; + [_board addSublayer: grid]; + [grid release]; + + CGRect gridFrame = grid.frame; + CGFloat pieceSize = (int)grid.spacing.width & ~1; // make sure it's even + CGFloat captureHeight = gridFrame.size.height-4*pieceSize; + _captured[0] = [[Stack alloc] initWithStartPos: CGPointMake(2*pieceSize,0) + spacing: CGSizeMake(0,pieceSize) + wrapInterval: floor(captureHeight/pieceSize) + wrapSpacing: CGSizeMake(-pieceSize,0)]; + _captured[0].frame = CGRectMake(CGRectGetMinX(gridFrame)-3*pieceSize, + CGRectGetMinY(gridFrame)+3*pieceSize, + 2*pieceSize, captureHeight); + _captured[0].zPosition = kPieceZ+1; + [_board addSublayer: _captured[0]]; + [_captured[0] release]; + + _captured[1] = [[Stack alloc] initWithStartPos: CGPointMake(0,captureHeight) + spacing: CGSizeMake(0,-pieceSize) + wrapInterval: floor(captureHeight/pieceSize) + wrapSpacing: CGSizeMake(pieceSize,0)]; + _captured[1].frame = CGRectMake(CGRectGetMaxX(gridFrame)+pieceSize, + CGRectGetMinY(gridFrame)+pieceSize, + 2*pieceSize, captureHeight); + _captured[1].zPosition = kPieceZ+1; + [_board addSublayer: _captured[1]]; + [_captured[1] release]; + PreloadSound(@"Pop"); +} + +- (CGImageRef) iconForPlayer: (int)playerNum +{ + return GetCGImageNamed( playerNum ?@"bot086.png" :@"bot089.png" ); +} + +- (Piece*) pieceForPlayer: (int)index +{ + NSString *imageName = index ?@"bot086.png" :@"bot089.png"; + CGFloat pieceSize = (int)(_grid.spacing.width * 0.9) & ~1; // make sure it's even + Piece *stone = [[Piece alloc] initWithImageNamed: imageName scale: pieceSize]; + stone.owner = [self.players objectAtIndex: index]; + return [stone autorelease]; +} - (Bit*) bitToPlaceInHolder: (id)holder { if( holder.bit != nil || ! [holder isKindOfClass: [GoSquare class]] ) return nil; - NSString *imageName = self.currentPlayer.index ?@"White Ball.png" :@"Red Ball.png"; - CGFloat pieceSize = (int)(_grid.spacing.width * 0.9) & ~1; // make sure it's even - Piece *stone = [[Piece alloc] initWithImageNamed: imageName scale: pieceSize]; - stone.owner = self.currentPlayer; - return [stone autorelease]; + else + return [self pieceForPlayer: self.currentPlayer.index]; } @@ -143,7 +160,7 @@ - (void) bit: (Bit*)bit movedFrom: (id)srcHolder to: (id)dstHolder { Square *dst=(Square*)dstHolder; - int curIndex = _currentPlayer.index; + int curIndex = self.currentPlayer.index; // Check for captured enemy groups: BOOL captured = NO; for( GridCell *c in dst.neighbors ) @@ -158,8 +175,9 @@ } if( captured ) PlaySound(@"Pop"); - - [self nextPlayer]; + + [self.currentTurn addToMove: dst.name]; + [self endTurn]; } @@ -167,4 +185,62 @@ // both of which are rather complex to decide in Go. +#pragma mark - +#pragma mark STATE: + + +- (NSString*) stateString +{ + int n = _grid.rows; + unichar state[n*n]; + for( int y=0; y +@class Game; + + +/** A mostly-passive object used to represent a player. */ +@interface Player : NSObject +{ + Game *_game; + NSString *_name, *_uuid, *_address, *_addressType; + BOOL _local; +} + +- (id) initWithGame: (Game*)game; +- (id) initWithName: (NSString*)name; + +- (id) initWithCoder: (NSCoder*)decoder; +- (void) encodeWithCoder: (NSCoder*)coder; + +@property (readonly) Game *game; +@property (copy) NSString *name, // Display name + *UUID, // Address Book UUID + *address, // Contact address + *addressType; // Contact address type (an AB property type) +@property (readonly) int index; // Player's index in the Game's -players array +@property (readwrite,getter=isLocal) BOOL local; // Is the player on this computer? (Defaults to YES) +@property (readonly, getter=isCurrent) BOOL current; // Is it this player's turn? +@property (readonly, getter=isFriendly) BOOL friendly; // Is this player the current player or an ally? +@property (readonly, getter=isUnfriendly) BOOL unfriendly; // Is this player an opponent of the current player? +@property (readonly) Player *nextPlayer, *previousPlayer; // The next/previous player in sequence +@property (readonly) CGImageRef icon; +@end + + + +@interface CALayer (Game) + +/** Called on any CALayer in the game's layer tree, will return the current Game object. */ +@property (readonly) Game *game; + +@end diff -r a59acc683080 -r 6c78cc6bd7a6 Source/Player.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Source/Player.m Thu Jul 03 17:44:30 2008 -0700 @@ -0,0 +1,105 @@ +// +// Player.m +// YourMove +// +// Created by Jens Alfke on 7/3/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "Player.h" +#import "Game.h" + + +#pragma mark - +@implementation Player + + +- (id) initWithGame: (Game*)game +{ + self = [super init]; + if (self != nil) { + _game = game; + _local = YES; + } + return self; +} + +- (id) initWithName: (NSString*)name +{ + self = [super init]; + if (self != nil) { + self.name = name; + } + return self; +} + + +- (id) initWithCoder: (NSCoder*)decoder +{ + self = [self init]; + if( self ) { + _game = [decoder decodeObjectForKey: @"game"]; + _name = [[decoder decodeObjectForKey: @"name"] copy]; + _uuid = [[decoder decodeObjectForKey: @"UUID"] copy]; + _address = [[decoder decodeObjectForKey: @"address"] copy]; + _addressType = [[decoder decodeObjectForKey: @"addressType"] copy]; + _local= [decoder decodeBoolForKey: @"local"]; + } + return self; +} + +- (void) encodeWithCoder: (NSCoder*)coder +{ + [coder encodeObject: _game forKey: @"game"]; + [coder encodeObject: _name forKey: @"name"]; + [coder encodeObject: _uuid forKey: @"UUID"]; + [coder encodeObject: _address forKey: @"address"]; + [coder encodeObject: _addressType forKey: @"addressType"]; + [coder encodeBool: _local forKey: @"local"]; +} + +- (void) dealloc +{ + [_name release]; + [_uuid release]; + [_address release]; + [_addressType release]; + [super dealloc]; +} + + +@synthesize game=_game, name=_name, UUID=_uuid, address=_address, addressType=_addressType, local=_local; + +- (BOOL) isCurrent {return self == _game.currentPlayer;} +- (BOOL) isFriendly {return self == _game.currentPlayer;} // could be overridden for games with partners +- (BOOL) isUnfriendly {return ! self.friendly;} + ++ (NSArray*) keyPathsForValuesAffectingCurrent {return [NSArray arrayWithObject: @"game.currentPlayer"];} + + +- (int) index +{ + return [_game.players indexOfObjectIdenticalTo: self]; +} + +- (Player*) nextPlayer +{ + return [_game.players objectAtIndex: (self.index+1) % _game.players.count]; +} + +- (Player*) previousPlayer +{ + return [_game.players objectAtIndex: (self.index-1) % _game.players.count]; +} + +- (CGImageRef) icon +{ + return [_game iconForPlayer: self.index]; +} + +- (NSString*) description +{ + return [NSString stringWithFormat: @"%@[%@]", self.class,self.name]; +} + +@end diff -r a59acc683080 -r 6c78cc6bd7a6 Source/QuartzUtils.h --- a/Source/QuartzUtils.h Thu May 29 15:04:06 2008 -0700 +++ b/Source/QuartzUtils.h Thu Jul 03 17:44:30 2008 -0700 @@ -26,7 +26,7 @@ /** Constants for various commonly used colors. */ extern CGColorRef kBlackColor, kWhiteColor, kTranslucentGrayColor, kTranslucentLightGrayColor, - kAlmostInvisibleWhiteColor, + kTranslucentWhiteColor, kAlmostInvisibleWhiteColor, kHighlightColor; #if TARGET_OS_IPHONE diff -r a59acc683080 -r 6c78cc6bd7a6 Source/QuartzUtils.m --- a/Source/QuartzUtils.m Thu May 29 15:04:06 2008 -0700 +++ b/Source/QuartzUtils.m Thu Jul 03 17:44:30 2008 -0700 @@ -27,7 +27,7 @@ CGColorRef kBlackColor, kWhiteColor, kTranslucentGrayColor, kTranslucentLightGrayColor, - kAlmostInvisibleWhiteColor, + kTranslucentWhiteColor, kAlmostInvisibleWhiteColor, kHighlightColor; @@ -38,6 +38,7 @@ kWhiteColor = CreateGray(1.0, 1.0); kTranslucentGrayColor = CreateGray(0.0, 0.5); kTranslucentLightGrayColor = CreateGray(0.0, 0.25); + kTranslucentWhiteColor = CreateGray(1, 0.25); kAlmostInvisibleWhiteColor = CreateGray(1, 0.05); kHighlightColor = CreateRGB(1, 1, 0, 0.5); } diff -r a59acc683080 -r 6c78cc6bd7a6 Source/TicTacToeGame.h --- a/Source/TicTacToeGame.h Thu May 29 15:04:06 2008 -0700 +++ b/Source/TicTacToeGame.h Thu Jul 03 17:44:30 2008 -0700 @@ -20,7 +20,7 @@ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#import "Game.h" +#import "Game+Protected.h" @class RectGrid, Dispenser; diff -r a59acc683080 -r 6c78cc6bd7a6 Source/TicTacToeGame.m --- a/Source/TicTacToeGame.m Thu May 29 15:04:06 2008 -0700 +++ b/Source/TicTacToeGame.m Thu Jul 03 17:44:30 2008 -0700 @@ -38,42 +38,44 @@ return [p autorelease]; } -- (id) initWithBoard: (GGBLayer*)board +- (id) init { - self = [super initWithBoard: board]; + self = [super init]; if (self != nil) { [self setNumberOfPlayers: 2]; - - // Create a 3x3 grid: - CGFloat center = floor(CGRectGetMidX(board.bounds)); - _grid = [[RectGrid alloc] initWithRows: 3 columns: 3 frame: CGRectMake(center-150,0, 300,300)]; - [_grid addAllCells]; - _grid.allowsMoves = _grid.allowsCaptures = NO; - _grid.cellColor = CreateGray(1.0, 0.25); - _grid.lineColor = kTranslucentLightGrayColor; - [board addSublayer: _grid]; - - // Create piece dispensers for the two players: - for( int playerNumber=0; playerNumber<=1; playerNumber++ ) { - Piece *p = [self pieceForPlayer: playerNumber]; - CGFloat x = floor(CGRectGetMidX(_board.bounds)); -#if TARGET_OS_IPHONE - x = x - 80 + 160*playerNumber; - CGFloat y = 360; -#else - x += (playerNumber==0 ?-230 :230); - CGFloat y = 175; -#endif - _dispenser[playerNumber] = [[Dispenser alloc] initWithPrototype: p quantity: 0 - frame: CGRectMake(x-45,y-45, 90,90)]; - [_board addSublayer: _dispenser[playerNumber]]; - } - - // And they're off! - [self nextPlayer]; } return self; } + +- (void) setUpBoard +{ + // Create a 3x3 grid: + CGFloat center = floor(CGRectGetMidX(_board.bounds)); + [_grid release]; + _grid = [[RectGrid alloc] initWithRows: 3 columns: 3 frame: CGRectMake(center-150,0, 300,300)]; + [_grid addAllCells]; + _grid.allowsMoves = _grid.allowsCaptures = NO; + _grid.cellColor = CreateGray(1.0, 0.25); + _grid.lineColor = kTranslucentLightGrayColor; + [_board addSublayer: _grid]; + + // Create piece dispensers for the two players: + for( int playerNumber=0; playerNumber<=1; playerNumber++ ) { + Piece *p = [self pieceForPlayer: playerNumber]; + CGFloat x = floor(CGRectGetMidX(_board.bounds)); +#if TARGET_OS_IPHONE + x = x - 80 + 160*playerNumber; + CGFloat y = 360; +#else + x += (playerNumber==0 ?-230 :230); + CGFloat y = 175; +#endif + [_dispenser[playerNumber] release]; + _dispenser[playerNumber] = [[Dispenser alloc] initWithPrototype: p quantity: 0 + frame: CGRectMake(x-45,y-45, 90,90)]; + [_board addSublayer: _dispenser[playerNumber]]; + } +} - (NSString*) stateString @@ -92,12 +94,13 @@ - (void) setStateString: (NSString*)stateString { for( int i=0; i<9; i++ ) { - Piece *piece; - switch( [stateString characterAtIndex: i] ) { - case 'X': case 'x': piece = [self pieceForPlayer: 0]; break; - case 'O': case 'o': piece = [self pieceForPlayer: 1]; break; - default: piece = nil; break; - } + Piece *piece = nil; + if( i < stateString.length ) + switch( [stateString characterAtIndex: i] ) { + case 'X': case 'x': piece = [self pieceForPlayer: 0]; break; + case 'O': case 'o': piece = [self pieceForPlayer: 1]; break; + default: break; + } [_grid cellAtRow: i/3 column: i%3].bit = piece; } } @@ -116,16 +119,18 @@ { Square *square = (Square*)dst; int squareIndex = 3*square.row + square.column; - [self.currentMove appendFormat: @"%@%i", bit.name, squareIndex]; + [self.currentTurn addToMove: [NSString stringWithFormat: @"%@%i", bit.name, squareIndex]]; [super bit: bit movedFrom: src to: dst]; } +/* FIX: Need to restore this somehow, now that -nextPlayer is gone - (void) nextPlayer { [super nextPlayer]; // Give the next player another piece to put down: _dispenser[self.currentPlayer.index].quantity = 1; } + */ static Player* ownerAt( Grid *grid, int index ) { diff -r a59acc683080 -r 6c78cc6bd7a6 Source/Turn.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Source/Turn.h Thu Jul 03 17:44:30 2008 -0700 @@ -0,0 +1,61 @@ +// +// Turn.h +// YourMove +// +// Created by Jens Alfke on 7/3/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import +@class Game, Player; + + +typedef enum { + kTurnEmpty, // No action yet + kTurnPartial, // Action taken, but more needs to be done + kTurnComplete, // Action complete, but player needs to confirm + kTurnFinished // Turn is confirmed and finished +} TurnStatus; + + +extern NSString* const kTurnCompleteNotification; + + +/** A record of a particular turn in a Game, including the player's move and the resulting state. */ +@interface Turn : NSObject +{ + Game *_game; + Player *_player; + TurnStatus _status; + NSString *_move; + NSString *_boardState; + NSDate *_date; + NSString *_comment; + BOOL _replaying; +} + +- (id) initWithPlayer: (Player*)player; +- (id) initStartOfGame: (Game*)game; + +@property (readonly) Game *game; +@property (readonly) Player *player, *nextPlayer; +@property (readonly) Turn *previousTurn; +@property (readonly) unsigned turnNumber; +@property (readonly) BOOL isLatestTurn; + +@property TurnStatus status; + +@property (readonly,copy) NSString *move; +@property (readonly,copy) NSString *boardState; +@property (readonly,retain)NSDate *date; +@property (copy) NSString *comment; + +/** Appends to the move string. Only allowed if the status is Empty or Partial. */ +- (void) addToMove: (NSString*)move; + +/** Copies the current state of the Game's board to my boardState */ +- (void) captureBoardState; + +@property BOOL replaying; + +@end diff -r a59acc683080 -r 6c78cc6bd7a6 Source/Turn.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Source/Turn.m Thu Jul 03 17:44:30 2008 -0700 @@ -0,0 +1,161 @@ +// +// Turn.m +// YourMove +// +// Created by Jens Alfke on 7/3/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "Turn.h" +#import "Game+Protected.h" +#import "Player.h" + + +NSString* const kTurnCompleteNotification = @"TurnComplete"; + + +@interface Turn () +@property (copy) NSString *move, *boardState; +@property (retain) NSDate *date; +@end + + + +@implementation Turn + + +- (id) initWithPlayer: (Player*)player +{ + NSParameterAssert(player!=nil); + self = [super init]; + if (self != nil) { + _game = player.game; + _player = player; + _status = kTurnEmpty; + self.boardState = _game.latestTurn.boardState; + } + return self; +} + +- (id) initStartOfGame: (Game*)game +{ + NSParameterAssert(game!=nil); + self = [super init]; + if (self != nil) { + _game = game; + _status = kTurnFinished; + self.boardState = game.initialStateString; + self.date = [NSDate date]; + } + return self; +} + + +- (id) initWithCoder: (NSCoder*)decoder +{ + self = [self init]; + if( self ) { + _game = [decoder decodeObjectForKey: @"game"]; + _player = [decoder decodeObjectForKey: @"player"]; + _status = [decoder decodeIntForKey: @"status"]; + _move = [[decoder decodeObjectForKey: @"move"] copy]; + _boardState = [[decoder decodeObjectForKey: @"boardState"] copy]; + _date = [[decoder decodeObjectForKey: @"date"] copy]; + _comment = [[decoder decodeObjectForKey: @"comment"] copy]; + } + return self; +} + +- (void) encodeWithCoder: (NSCoder*)coder +{ + [coder encodeObject: _game forKey: @"game"]; + [coder encodeObject: _player forKey: @"player"]; + [coder encodeInt: _status forKey: @"status"]; + [coder encodeObject: _move forKey: @"move"]; + [coder encodeObject: _boardState forKey: @"boardState"]; + [coder encodeObject: _date forKey: @"date"]; + [coder encodeObject: _comment forKey: @"comment"]; +} + +- (void) dealloc +{ + [_move release]; + [_boardState release]; + [_date release]; + [_comment release]; + [super dealloc]; +} + + +- (NSString*) description +{ + return [NSString stringWithFormat: @"%@[%@, #%i, %@]", self.class, _game.class, self.turnNumber, _move]; +} + + +@synthesize game=_game, player=_player, move=_move, boardState=_boardState, date=_date, comment=_comment, + replaying=_replaying; + + +- (unsigned) turnNumber {return [_game.turns indexOfObjectIdenticalTo: self];} +- (BOOL) isLatestTurn {return _game.turns.lastObject == self;} +- (Turn*) previousTurn {return [_game.turns objectAtIndex: self.turnNumber-1];} +- (Player*) nextPlayer {return _player ?_player.nextPlayer :[_game.players objectAtIndex: 0];} + +- (TurnStatus) status {return _status;} + +- (void) setStatus: (TurnStatus)status +{ + BOOL ok = NO; + switch( _status ) { + case kTurnEmpty: + ok = (status==kTurnPartial) || (status==kTurnComplete); + break; + case kTurnPartial: + ok = (status==kTurnEmpty) || (status==kTurnComplete) || (status==kTurnFinished); + break; + case kTurnComplete: + ok = (status==kTurnEmpty) || (status==kTurnPartial) || (status==kTurnFinished); + break; + case kTurnFinished: + break; + } + NSAssert2(ok,@"Illegal Turn status transition %i -> %i", _status,status); + + [self captureBoardState]; + _status = status; + if( _status==kTurnEmpty ) { + self.move = nil; + self.date = nil; + } else + self.date = [NSDate date]; +} + + +- (void) addToMove: (NSString*)move +{ + if( ! _replaying ) { + NSParameterAssert(move.length); + NSAssert(_status