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