Source/Game.m
author Jens Alfke <jens@mooseyard.com>
Sun Mar 16 15:06:47 2008 -0700 (2008-03-16)
changeset 7 428a194e3e59
parent 4 d781b00f3ed4
child 8 45c82a071aca
permissions -rw-r--r--
Game class now tracks board state and moves, as strings, and can step through its history.
Fixed another bug in Go (you could drag your captured stones back to the board!)
     1 /*  This code is based on Apple's "GeekGameBoard" sample code, version 1.0.
     2     http://developer.apple.com/samplecode/GeekGameBoard/
     3     Copyright © 2007 Apple Inc. Copyright © 2008 Jens Alfke. All Rights Reserved.
     4 
     5     Redistribution and use in source and binary forms, with or without modification, are permitted
     6     provided that the following conditions are met:
     7 
     8     * Redistributions of source code must retain the above copyright notice, this list of conditions
     9       and the following disclaimer.
    10     * Redistributions in binary form must reproduce the above copyright notice, this list of
    11       conditions and the following disclaimer in the documentation and/or other materials provided
    12       with the distribution.
    13 
    14     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
    15     IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
    16     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
    17     BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    18     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
    19     PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
    20     CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
    21     THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    22 */
    23 #import "Game.h"
    24 #import "Bit.h"
    25 #import "BitHolder.h"
    26 #import "QuartzUtils.h"
    27 
    28 
    29 @interface Game ()
    30 @property (copy) NSArray *players;
    31 @property (assign) Player *currentPlayer, *winner;
    32 @end
    33 
    34 
    35 @implementation Game
    36 
    37 
    38 + (NSString*) displayName
    39 {
    40     NSString* name = [self description];
    41     if( [name hasSuffix: @"Game"] )
    42         name = [name substringToIndex: name.length-4];
    43     return name;
    44 }
    45 
    46 
    47 - (id) initWithBoard: (GGBLayer*)board
    48 {
    49     self = [super init];
    50     if (self != nil) {
    51         _states = [[NSMutableArray alloc] init];
    52         _moves = [[NSMutableArray alloc] init];
    53         _currentMove = [[NSMutableString alloc] init];
    54         _board = [board retain];
    55         // Store a pointer to myself as the value of the "Game" property
    56         // of my root layer. (CALayers can have arbitrary KV properties stored into them.)
    57         // This is used by the -[CALayer game] category method defined below, to find the Game.
    58         [board setValue: self forKey: @"Game"];
    59     }
    60     return self;
    61 }
    62 
    63 
    64 - (void) dealloc
    65 {
    66     [_board release];
    67     [_players release];
    68     [_currentMove release];
    69     [_states release];
    70     [_moves release];
    71     [super dealloc];
    72 }
    73 
    74 
    75 @synthesize players=_players, currentPlayer=_currentPlayer, winner=_winner, 
    76             currentMove=_currentMove, states=_states, moves=_moves;
    77 
    78 
    79 - (void) setNumberOfPlayers: (unsigned)n
    80 {
    81     NSMutableArray *players = [NSMutableArray arrayWithCapacity: n];
    82     for( int i=1; i<=n; i++ ) {
    83         Player *player = [[Player alloc] initWithGame: self];
    84         player.name = [NSString stringWithFormat: @"Player %i",i];
    85         [players addObject: player];
    86         [player release];
    87     }
    88     self.winner = nil;
    89     self.currentPlayer = nil;
    90     self.players = players;
    91 }
    92 
    93 
    94 - (void) addToMove: (NSString*)str;
    95 {
    96     [_currentMove appendString: str];
    97 }
    98 
    99 
   100 - (BOOL) _rememberState
   101 {
   102     if( self.isLatestTurn ) {
   103         [_states addObject: self.stateString];
   104         return YES;
   105     } else
   106         return NO;
   107 }
   108 
   109 
   110 - (void) nextPlayer
   111 {
   112     BOOL latestTurn = [self _rememberState];
   113     if( ! _currentPlayer ) {
   114         NSLog(@"*** The %@ Begins! ***", self.class);
   115         self.currentPlayer = [_players objectAtIndex: 0];
   116     } else {
   117         self.currentPlayer = _currentPlayer.nextPlayer;
   118         if( latestTurn ) {
   119             [self willChangeValueForKey: @"currentTurn"];
   120             _currentTurn++;
   121             [self didChangeValueForKey: @"currentTurn"];
   122         }
   123     }
   124     NSLog(@"Current player is %@",_currentPlayer);
   125 }
   126 
   127 
   128 - (void) endTurn
   129 {
   130     NSLog(@"--- End of turn (move was '%@')", _currentMove);
   131     if( self.isLatestTurn ) {
   132         [self willChangeValueForKey: @"maxTurn"];
   133         [_moves addObject: [[_currentMove copy] autorelease]];
   134         [_currentMove setString: @""];
   135         [self didChangeValueForKey: @"maxTurn"];
   136     }
   137 
   138     Player *winner = [self checkForWinner];
   139     if( winner ) {
   140         NSLog(@"*** The %@ Ends! The winner is %@ ! ***", self.class, winner);
   141         [self _rememberState];
   142         self.winner = winner;
   143     } else
   144         [self nextPlayer];
   145 }
   146 
   147 
   148 - (unsigned) maxTurn
   149 {
   150     return _moves.count;
   151 }
   152 
   153 - (unsigned) currentTurn
   154 {
   155     return _currentTurn;
   156 }
   157 
   158 - (void) setCurrentTurn: (unsigned)turn
   159 {
   160     NSParameterAssert(turn<=self.maxTurn);
   161     if( turn != _currentTurn ) {
   162         if( turn==_currentTurn+1 ) {
   163             [self applyMoveString: [_moves objectAtIndex: _currentTurn]];
   164         } else {
   165             [CATransaction begin];
   166             [CATransaction setValue:(id)kCFBooleanTrue
   167                              forKey:kCATransactionDisableActions];
   168             self.stateString = [_states objectAtIndex: turn];
   169             [CATransaction commit];
   170         }
   171         _currentTurn = turn;
   172         self.currentPlayer = [_players objectAtIndex: (turn % _players.count)];
   173     }
   174 }
   175 
   176 
   177 - (BOOL) isLatestTurn
   178 {
   179     return _currentTurn == MAX(_states.count,1)-1;
   180 }
   181 
   182 
   183 - (BOOL) animateMoveFrom: (BitHolder*)src to: (BitHolder*)dst
   184 {
   185     if( src==nil || dst==nil || dst==src )
   186         return NO;
   187     Bit *bit = [src canDragBit: src.bit];
   188     if( ! bit || ! [dst canDropBit: bit atPoint: GetCGRectCenter(dst.bounds)]
   189               || ! [self canBit: bit moveFrom: src to: dst] )
   190         return NO;
   191     
   192     ChangeSuperlayer(bit, _board.superlayer, -1);
   193     bit.pickedUp = YES;
   194     dst.highlighted = YES;
   195     [bit performSelector: @selector(setPickedUp:) withObject:nil afterDelay: 0.15];
   196     CGPoint endPosition = [dst convertPoint: GetCGRectCenter(dst.bounds) toLayer: bit.superlayer];
   197     [bit animateAndBlock: @"position"
   198                     from: [NSValue valueWithPoint: NSPointFromCGPoint(bit.position)]
   199                       to: [NSValue valueWithPoint: NSPointFromCGPoint(endPosition)]
   200                 duration: 0.25];
   201     dst.bit = bit;
   202     dst.highlighted = NO;
   203     bit.pickedUp = NO;
   204     
   205     [src draggedBit: bit to: dst];
   206     [self bit: bit movedFrom: src to: dst];
   207     src = dst;
   208     return YES;
   209 }
   210      
   211 
   212 #pragma mark -
   213 #pragma mark GAMEPLAY METHODS TO BE OVERRIDDEN:
   214 
   215 
   216 + (BOOL) landscapeOriented
   217 {
   218     return NO;
   219 }
   220 
   221 
   222 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src
   223 {
   224     return YES;
   225 }
   226 
   227 - (BOOL) canBit: (Bit*)bit moveFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
   228 {
   229     return YES;
   230 }
   231 
   232 - (void) bit: (Bit*)bit movedFrom: (id<BitHolder>)src to: (id<BitHolder>)dst
   233 {
   234     [self endTurn];
   235 }
   236 
   237 - (Bit*) bitToPlaceInHolder: (id<BitHolder>)holder
   238 {
   239     return nil;
   240 }
   241 
   242 
   243 - (BOOL) clickedBit: (Bit*)bit
   244 {
   245     return YES;
   246 }
   247 
   248 - (Player*) checkForWinner
   249 {
   250     return nil;
   251 }
   252 
   253 
   254 - (NSString*) stateString                   {return @"";}
   255 - (void) setStateString: (NSString*)s       { }
   256 
   257 - (BOOL) applyMoveString: (NSString*)move   {return NO;}
   258 
   259 @end
   260 
   261 
   262 
   263 
   264 @implementation Player
   265 
   266 
   267 - (id) initWithGame: (Game*)game
   268 {
   269     self = [super init];
   270     if (self != nil) {
   271         _game = game;
   272     }
   273     return self;
   274 }
   275 
   276 
   277 @synthesize game=_game, name=_name;
   278 
   279 - (BOOL) isCurrent      {return self == _game.currentPlayer;}
   280 - (BOOL) isFriendly     {return self == _game.currentPlayer;}   // could be overridden for games with partners
   281 - (BOOL) isUnfriendly   {return ! self.friendly;}
   282 
   283 - (int) index
   284 {
   285     return [_game.players indexOfObjectIdenticalTo: self];
   286 }
   287 
   288 - (Player*) nextPlayer
   289 {
   290     return [_game.players objectAtIndex: (self.index+1) % _game.players.count];
   291 }
   292 
   293 - (Player*) previousPlayer
   294 {
   295     return [_game.players objectAtIndex: (self.index-1) % _game.players.count];
   296 }
   297 
   298 - (NSString*) description
   299 {
   300     return [NSString stringWithFormat: @"%@[%@]", self.class,self.name];
   301 }
   302 
   303 @end
   304 
   305 
   306 
   307 
   308 @implementation CALayer (Game)
   309 
   310 - (Game*) game
   311 {
   312     // The Game object stores a pointer to itself as the value of the "Game" property
   313     // of its root layer. (CALayers can have arbitrary KV properties stored into them.)
   314     for( CALayer *layer = self; layer; layer=layer.superlayer ) {
   315         Game *game = [layer valueForKey: @"Game"];
   316         if( game )
   317             return game;
   318     }
   319     NSAssert1(NO,@"Couldn't look up Game from %@",self);
   320     return nil;
   321 }
   322 
   323 @end