Source/BoardView.m
author Jens Alfke <jens@mooseyard.com>
Tue Jul 08 13:12:01 2008 -0700 (2008-07-08)
changeset 13 db7bb080c3d5
parent 10 6c78cc6bd7a6
child 14 4585c74d809c
permissions -rw-r--r--
Fixed some memory leaks, and took the address-related properties out of Player.
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 "BoardView.h"
jens@0
    24
#import "Bit.h"
jens@0
    25
#import "BitHolder.h"
jens@0
    26
#import "Game.h"
jens@12
    27
#import "Turn.h"
jens@10
    28
#import "Player.h"
jens@0
    29
#import "QuartzUtils.h"
jens@0
    30
#import "GGBUtils.h"
jens@0
    31
jens@0
    32
jens@3
    33
@interface BoardView ()
jens@3
    34
- (void) _findDropTarget: (NSPoint)pos;
jens@3
    35
@end
jens@3
    36
jens@3
    37
jens@0
    38
@implementation BoardView
jens@0
    39
jens@0
    40
jens@10
    41
@synthesize gameboard=_gameboard;
jens@0
    42
jens@0
    43
jens@0
    44
- (void) dealloc
jens@0
    45
{
jens@0
    46
    [_game release];
jens@0
    47
    [super dealloc];
jens@0
    48
}
jens@0
    49
jens@0
    50
jens@10
    51
- (void) _removeGameBoard
jens@10
    52
{
jens@10
    53
    if( _gameboard ) {
jens@10
    54
        RemoveImmediately(_gameboard);
jens@10
    55
        _gameboard = nil;
jens@10
    56
    }
jens@10
    57
}
jens@10
    58
jens@10
    59
- (void) createGameBoard
jens@10
    60
{
jens@10
    61
    [self _removeGameBoard];
jens@10
    62
    _gameboard = [[CALayer alloc] init];
jens@10
    63
    _gameboard.frame = [self gameBoardFrame];
jens@10
    64
    _gameboard.autoresizingMask = kCALayerMinXMargin | kCALayerMaxXMargin | kCALayerMinYMargin | kCALayerMaxYMargin;
jens@10
    65
jens@10
    66
    // Tell the game to set up the board:
jens@10
    67
    _game.board = _gameboard;
jens@10
    68
jens@10
    69
    [self.layer addSublayer: _gameboard];
jens@10
    70
    [_gameboard release];
jens@10
    71
}
jens@10
    72
jens@10
    73
jens@10
    74
- (void) reverseBoard
jens@10
    75
{
jens@10
    76
    [_gameboard setValue: [NSNumber numberWithDouble: M_PI]
jens@10
    77
              forKeyPath: @"transform.rotation"];
jens@10
    78
}
jens@10
    79
jens@10
    80
jens@10
    81
- (Game*) game
jens@10
    82
{
jens@10
    83
    return _game;
jens@10
    84
}
jens@10
    85
jens@10
    86
- (void) setGame: (Game*)game
jens@10
    87
{
jens@10
    88
    if( game!=_game ) {
jens@10
    89
        setObj(&_game,game);
jens@10
    90
        [self createGameBoard];
jens@10
    91
    }
jens@10
    92
}
jens@10
    93
jens@0
    94
- (void) startGameNamed: (NSString*)gameClassName
jens@0
    95
{
jens@10
    96
    Class gameClass = NSClassFromString(gameClassName);
jens@10
    97
    Game *game = [[gameClass alloc] init];
jens@10
    98
    if( game ) {
jens@10
    99
        self.game = game;
jens@10
   100
        [game release];
jens@0
   101
    }
jens@10
   102
}
jens@10
   103
jens@10
   104
jens@10
   105
- (BOOL) canMakeMove
jens@10
   106
{
jens@12
   107
    return _game != nil
jens@12
   108
        && _game.currentPlayer.local
jens@12
   109
        && _game.currentTurn.status < kTurnComplete;
jens@0
   110
}
jens@0
   111
jens@0
   112
jens@0
   113
- (CGRect) gameBoardFrame
jens@0
   114
{
jens@0
   115
    return self.layer.bounds;
jens@0
   116
}
jens@0
   117
jens@0
   118
jens@0
   119
- (void)resetCursorRects
jens@0
   120
{
jens@0
   121
    [super resetCursorRects];
jens@10
   122
    if( self.canMakeMove )
jens@10
   123
        [self addCursorRect: self.bounds cursor: [NSCursor openHandCursor]];
jens@0
   124
}
jens@0
   125
jens@0
   126
jens@0
   127
- (IBAction) enterFullScreen: (id)sender
jens@0
   128
{
jens@10
   129
    [self _removeGameBoard];
jens@0
   130
    if( self.isInFullScreenMode ) {
jens@0
   131
        [self exitFullScreenModeWithOptions: nil];
jens@0
   132
    } else {
jens@0
   133
        [self enterFullScreenMode: self.window.screen 
jens@0
   134
                      withOptions: nil];
jens@0
   135
    }
jens@10
   136
    [self createGameBoard];
jens@10
   137
}
jens@10
   138
jens@10
   139
jens@10
   140
- (void)viewWillStartLiveResize
jens@10
   141
{
jens@10
   142
    [super viewWillStartLiveResize];
jens@10
   143
    _oldSize = self.frame.size;
jens@10
   144
}
jens@10
   145
jens@10
   146
- (void)setFrameSize:(NSSize)newSize
jens@10
   147
{
jens@10
   148
    [super setFrameSize: newSize];
jens@10
   149
    if( _oldSize.width > 0.0f ) {
jens@10
   150
        CGAffineTransform xform = _gameboard.affineTransform;
jens@10
   151
        xform.a = xform.d = MIN(newSize.width,newSize.height)/MIN(_oldSize.width,_oldSize.height);
jens@10
   152
        _gameboard.affineTransform = xform;
jens@10
   153
    } else
jens@10
   154
        [self createGameBoard];
jens@10
   155
}
jens@10
   156
jens@10
   157
- (void)viewDidEndLiveResize
jens@10
   158
{
jens@10
   159
    [super viewDidEndLiveResize];
jens@10
   160
    _oldSize.width = _oldSize.height = 0.0f;
jens@10
   161
    [self createGameBoard];
jens@0
   162
}
jens@0
   163
jens@0
   164
jens@0
   165
#pragma mark -
jens@0
   166
#pragma mark KEY EVENTS:
jens@0
   167
jens@0
   168
jens@0
   169
- (void) keyDown: (NSEvent*)ev
jens@0
   170
{
jens@0
   171
    if( self.isInFullScreenMode ) {
jens@0
   172
        if( [ev.charactersIgnoringModifiers hasPrefix: @"\033"] )       // Esc key
jens@0
   173
            [self enterFullScreen: self];
jens@0
   174
    }
jens@0
   175
}
jens@0
   176
jens@0
   177
jens@0
   178
#pragma mark -
jens@0
   179
#pragma mark HIT-TESTING:
jens@0
   180
jens@0
   181
jens@5
   182
/** Converts a point from window coords, to this view's root layer's coords. */
jens@5
   183
- (CGPoint) _convertPointFromWindowToLayer: (NSPoint)locationInWindow
jens@5
   184
{
jens@5
   185
    NSPoint where = [self convertPoint: locationInWindow fromView: nil];    // convert to view coords
jens@5
   186
    return NSPointToCGPoint( [self convertPointToBase: where] );            // then to layer coords
jens@5
   187
}
jens@5
   188
jens@5
   189
jens@0
   190
// Hit-testing callbacks (to identify which layers caller is interested in):
jens@0
   191
typedef BOOL (*LayerMatchCallback)(CALayer*);
jens@0
   192
jens@0
   193
static BOOL layerIsBit( CALayer* layer )        {return [layer isKindOfClass: [Bit class]];}
jens@0
   194
static BOOL layerIsBitHolder( CALayer* layer )  {return [layer conformsToProtocol: @protocol(BitHolder)];}
jens@0
   195
static BOOL layerIsDropTarget( CALayer* layer ) {return [layer respondsToSelector: @selector(draggingEntered:)];}
jens@0
   196
jens@0
   197
jens@0
   198
/** Locates the layer at a given point in window coords.
jens@0
   199
    If the leaf layer doesn't pass the layer-match callback, the nearest ancestor that does is returned.
jens@0
   200
    If outOffset is provided, the point's position relative to the layer is stored into it. */
jens@0
   201
- (CALayer*) hitTestPoint: (NSPoint)locationInWindow
jens@0
   202
         forLayerMatching: (LayerMatchCallback)match
jens@0
   203
                   offset: (CGPoint*)outOffset
jens@0
   204
{
jens@5
   205
    CGPoint where = [self _convertPointFromWindowToLayer: locationInWindow ];
jens@0
   206
    CALayer *layer = [_gameboard hitTest: where];
jens@0
   207
    while( layer ) {
jens@0
   208
        if( match(layer) ) {
jens@0
   209
            CGPoint bitPos = [self.layer convertPoint: layer.position 
jens@0
   210
                              fromLayer: layer.superlayer];
jens@0
   211
            if( outOffset )
jens@0
   212
                *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y);
jens@0
   213
            return layer;
jens@0
   214
        } else
jens@0
   215
            layer = layer.superlayer;
jens@0
   216
    }
jens@0
   217
    return nil;
jens@0
   218
}
jens@0
   219
jens@0
   220
jens@0
   221
#pragma mark -
jens@0
   222
#pragma mark MOUSE CLICKS & DRAGS:
jens@0
   223
jens@0
   224
jens@0
   225
- (void) mouseDown: (NSEvent*)ev
jens@0
   226
{
jens@10
   227
    if( ! self.canMakeMove ) {
jens@10
   228
        NSBeep();
jens@10
   229
        return;
jens@10
   230
    }
jens@10
   231
    
jens@3
   232
    BOOL placing = NO;
jens@0
   233
    _dragStartPos = ev.locationInWindow;
jens@0
   234
    _dragBit = (Bit*) [self hitTestPoint: _dragStartPos
jens@0
   235
                        forLayerMatching: layerIsBit 
jens@0
   236
                                  offset: &_dragOffset];
jens@3
   237
    
jens@3
   238
    if( ! _dragBit ) {
jens@3
   239
        // If no bit was clicked, see if it's a BitHolder the game will let the user add a Bit to:
jens@3
   240
        id<BitHolder> holder = (id<BitHolder>) [self hitTestPoint: _dragStartPos
jens@3
   241
                                                 forLayerMatching: layerIsBitHolder
jens@3
   242
                                                           offset: NULL];
jens@3
   243
        if( holder ) {
jens@3
   244
            _dragBit = [_game bitToPlaceInHolder: holder];
jens@3
   245
            if( _dragBit ) {
jens@3
   246
                _dragOffset.x = _dragOffset.y = 0;
jens@3
   247
                if( _dragBit.superlayer==nil )
jens@5
   248
                    _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
jens@3
   249
                placing = YES;
jens@3
   250
            }
jens@3
   251
        }
jens@3
   252
    }
jens@3
   253
    
jens@3
   254
    if( ! _dragBit ) {
jens@3
   255
        Beep();
jens@3
   256
        return;
jens@3
   257
    }
jens@3
   258
    
jens@3
   259
    // Clicked on a Bit:
jens@3
   260
    _dragMoved = NO;
jens@3
   261
    _dropTarget = nil;
jens@3
   262
    _oldHolder = _dragBit.holder;
jens@3
   263
    // Ask holder's and game's permission before dragging:
jens@3
   264
    if( _oldHolder ) {
jens@3
   265
        _dragBit = [_oldHolder canDragBit: _dragBit];
jens@0
   266
        if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) {
jens@0
   267
            [_oldHolder cancelDragBit: _dragBit];
jens@0
   268
            _dragBit = nil;
jens@0
   269
        }
jens@0
   270
        if( ! _dragBit ) {
jens@0
   271
            _oldHolder = nil;
jens@0
   272
            NSBeep();
jens@0
   273
            return;
jens@0
   274
        }
jens@3
   275
    }
jens@3
   276
    
jens@3
   277
    // Start dragging:
jens@3
   278
    _oldSuperlayer = _dragBit.superlayer;
jens@3
   279
    _oldLayerIndex = [_oldSuperlayer.sublayers indexOfObjectIdenticalTo: _dragBit];
jens@3
   280
    _oldPos = _dragBit.position;
jens@3
   281
    ChangeSuperlayer(_dragBit, self.layer, self.layer.sublayers.count);
jens@3
   282
    _dragBit.pickedUp = YES;
jens@3
   283
    [[NSCursor closedHandCursor] push];
jens@3
   284
    
jens@3
   285
    if( placing ) {
jens@3
   286
        if( _oldSuperlayer )
jens@5
   287
            _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
jens@3
   288
        _dragMoved = YES;
jens@3
   289
        [self _findDropTarget: _dragStartPos];
jens@3
   290
    }
jens@0
   291
}
jens@0
   292
jens@3
   293
jens@0
   294
- (void) mouseDragged: (NSEvent*)ev
jens@0
   295
{
jens@0
   296
    if( _dragBit ) {
jens@0
   297
        // Get the mouse position, and see if we've moved 3 pixels since the mouseDown:
jens@0
   298
        NSPoint pos = ev.locationInWindow;
jens@0
   299
        if( fabs(pos.x-_dragStartPos.x)>=3 || fabs(pos.y-_dragStartPos.y)>=3 )
jens@0
   300
            _dragMoved = YES;
jens@0
   301
        
jens@0
   302
        // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness):
jens@5
   303
        CGPoint where = [self _convertPointFromWindowToLayer: pos];
jens@0
   304
        where.x += _dragOffset.x;
jens@0
   305
        where.y += _dragOffset.y;
jens@0
   306
        
jens@5
   307
        CGPoint newPos = [_dragBit.superlayer convertPoint: where fromLayer: self.layer];
jens@0
   308
jens@0
   309
        [CATransaction flush];
jens@0
   310
        [CATransaction begin];
jens@0
   311
        [CATransaction setValue:(id)kCFBooleanTrue
jens@0
   312
                         forKey:kCATransactionDisableActions];
jens@0
   313
        _dragBit.position = newPos;
jens@0
   314
        [CATransaction commit];
jens@0
   315
jens@0
   316
        // Find what it's over:
jens@3
   317
        [self _findDropTarget: pos];
jens@3
   318
    }
jens@3
   319
}
jens@3
   320
jens@3
   321
jens@3
   322
- (void) _findDropTarget: (NSPoint)locationInWindow
jens@3
   323
{
jens@3
   324
    locationInWindow.x += _dragOffset.x;
jens@3
   325
    locationInWindow.y += _dragOffset.y;
jens@3
   326
    id<BitHolder> target = (id<BitHolder>) [self hitTestPoint: locationInWindow
jens@3
   327
                                             forLayerMatching: layerIsBitHolder
jens@3
   328
                                                       offset: NULL];
jens@3
   329
    if( target == _oldHolder )
jens@3
   330
        target = nil;
jens@3
   331
    if( target != _dropTarget ) {
jens@3
   332
        [_dropTarget willNotDropBit: _dragBit];
jens@3
   333
        _dropTarget.highlighted = NO;
jens@3
   334
        _dropTarget = nil;
jens@3
   335
    }
jens@3
   336
    if( target ) {
jens@3
   337
        CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position
jens@3
   338
                                                 fromLayer: _dragBit.superlayer];
jens@3
   339
        if( [target canDropBit: _dragBit atPoint: targetPos]
jens@3
   340
           && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) {
jens@3
   341
            _dropTarget = target;
jens@3
   342
            _dropTarget.highlighted = YES;
jens@0
   343
        }
jens@0
   344
    }
jens@0
   345
}
jens@0
   346
jens@3
   347
jens@0
   348
- (void) mouseUp: (NSEvent*)ev
jens@0
   349
{
jens@0
   350
    if( _dragBit ) {
jens@0
   351
        if( _dragMoved ) {
jens@0
   352
            // Update the drag tracking to the final mouse position:
jens@0
   353
            [self mouseDragged: ev];
jens@0
   354
            _dropTarget.highlighted = NO;
jens@0
   355
            _dragBit.pickedUp = NO;
jens@0
   356
jens@0
   357
            // Is the move legal?
jens@0
   358
            if( _dropTarget && [_dropTarget dropBit: _dragBit
jens@0
   359
                                            atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position 
jens@0
   360
                                                                            fromLayer: _dragBit.superlayer]] ) {
jens@0
   361
                // Yes, notify the interested parties:
jens@0
   362
                [_oldHolder draggedBit: _dragBit to: _dropTarget];
jens@0
   363
                [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget];
jens@0
   364
            } else {
jens@0
   365
                // Nope, cancel:
jens@0
   366
                [_dropTarget willNotDropBit: _dragBit];
jens@3
   367
                if( _oldSuperlayer ) {
jens@3
   368
                    ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
jens@3
   369
                    _dragBit.position = _oldPos;
jens@3
   370
                    [_oldHolder cancelDragBit: _dragBit];
jens@3
   371
                } else {
jens@3
   372
                    [_dragBit removeFromSuperlayer];
jens@3
   373
                }
jens@0
   374
            }
jens@0
   375
        } else {
jens@0
   376
            // Just a click, without a drag:
jens@0
   377
            _dropTarget.highlighted = NO;
jens@0
   378
            _dragBit.pickedUp = NO;
jens@0
   379
            ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
jens@0
   380
            [_oldHolder cancelDragBit: _dragBit];
jens@0
   381
            if( ! [_game clickedBit: _dragBit] )
jens@0
   382
                NSBeep();
jens@0
   383
        }
jens@9
   384
jens@0
   385
        _dropTarget = nil;
jens@0
   386
        _dragBit = nil;
jens@0
   387
        [NSCursor pop];
jens@0
   388
    }
jens@0
   389
}
jens@0
   390
jens@0
   391
jens@0
   392
#pragma mark -
jens@0
   393
#pragma mark INCOMING DRAGS:
jens@0
   394
jens@0
   395
jens@0
   396
// subroutine to call the target
jens@0
   397
static int tell( id target, SEL selector, id arg, int defaultValue )
jens@0
   398
{
jens@0
   399
    if( target && [target respondsToSelector: selector] )
jens@0
   400
        return (ssize_t) [target performSelector: selector withObject: arg];
jens@0
   401
    else
jens@0
   402
        return defaultValue;
jens@0
   403
}
jens@0
   404
jens@0
   405
jens@0
   406
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
jens@0
   407
{
jens@0
   408
    _viewDropTarget = [self hitTestPoint: [sender draggingLocation]
jens@0
   409
                        forLayerMatching: layerIsDropTarget
jens@0
   410
                                  offset: NULL];
jens@0
   411
    _viewDropOp = _viewDropTarget ?[_viewDropTarget draggingEntered: sender] :NSDragOperationNone;
jens@0
   412
    return _viewDropOp;
jens@0
   413
}
jens@0
   414
jens@0
   415
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
jens@0
   416
{
jens@0
   417
    CALayer *target = [self hitTestPoint: [sender draggingLocation]
jens@0
   418
                        forLayerMatching: layerIsDropTarget 
jens@0
   419
                                  offset: NULL];
jens@0
   420
    if( target == _viewDropTarget ) {
jens@0
   421
        if( _viewDropTarget )
jens@0
   422
            _viewDropOp = tell(_viewDropTarget,@selector(draggingUpdated:),sender,_viewDropOp);
jens@0
   423
    } else {
jens@0
   424
        tell(_viewDropTarget,@selector(draggingExited:),sender,0);
jens@0
   425
        _viewDropTarget = target;
jens@0
   426
        if( _viewDropTarget )
jens@0
   427
            _viewDropOp = [_viewDropTarget draggingEntered: sender];
jens@0
   428
        else
jens@0
   429
            _viewDropOp = NSDragOperationNone;
jens@0
   430
    }
jens@0
   431
    return _viewDropOp;
jens@0
   432
}
jens@0
   433
jens@0
   434
- (BOOL)wantsPeriodicDraggingUpdates
jens@0
   435
{
jens@0
   436
    return (_viewDropTarget!=nil);
jens@0
   437
}
jens@0
   438
jens@0
   439
- (void)draggingExited:(id <NSDraggingInfo>)sender
jens@0
   440
{
jens@0
   441
    tell(_viewDropTarget,@selector(draggingExited:),sender,0);
jens@0
   442
    _viewDropTarget = nil;
jens@0
   443
}
jens@0
   444
jens@0
   445
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
jens@0
   446
{
jens@0
   447
    return tell(_viewDropTarget,@selector(prepareForDragOperation:),sender,YES);
jens@0
   448
}
jens@0
   449
jens@0
   450
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
jens@0
   451
{
jens@0
   452
    return [_viewDropTarget performDragOperation: sender];
jens@0
   453
}
jens@0
   454
jens@0
   455
- (void)concludeDragOperation:(id <NSDraggingInfo>)sender
jens@0
   456
{
jens@0
   457
    tell(_viewDropTarget,@selector(concludeDragOperation:),sender,0);
jens@0
   458
}
jens@0
   459
jens@0
   460
- (void)draggingEnded:(id <NSDraggingInfo>)sender
jens@0
   461
{
jens@0
   462
    tell(_viewDropTarget,@selector(draggingEnded:),sender,0);
jens@0
   463
}
jens@0
   464
jens@0
   465
@end