Source/BoardUIView.m
author Jens Alfke <jens@mooseyard.com>
Thu May 29 15:04:06 2008 -0700 (2008-05-29)
changeset 9 a59acc683080
parent 8 45c82a071aca
child 10 6c78cc6bd7a6
permissions -rw-r--r--
Finally fixed the slow animation performance of board games; all it took was changing the board's z index from 1 to 0, somehow. Games working well now.
jens@1
     1
//
jens@1
     2
//  BoardUIView.m
jens@1
     3
//  GeekGameBoard
jens@1
     4
//
jens@1
     5
//  Created by Jens Alfke on 3/7/08.
jens@1
     6
//  Copyright 2008 __MyCompanyName__. All rights reserved.
jens@1
     7
//
jens@1
     8
jens@1
     9
#import "BoardUIView.h"
jens@1
    10
#import "Bit.h"
jens@1
    11
#import "BitHolder.h"
jens@1
    12
#import "Game.h"
jens@1
    13
#import "QuartzUtils.h"
jens@1
    14
#import "GGBUtils.h"
jens@1
    15
jens@1
    16
jens@3
    17
@interface BoardUIView ()
jens@3
    18
- (void) _findDropTarget: (CGPoint)pos;
jens@3
    19
@end
jens@3
    20
jens@3
    21
jens@1
    22
@implementation BoardUIView
jens@1
    23
jens@1
    24
jens@1
    25
- (id)initWithFrame:(CGRect)frame {
jens@1
    26
    if ( (self = [super initWithFrame:frame]) ) {
jens@1
    27
        // Initialization code here.
jens@1
    28
    }
jens@1
    29
    return self;
jens@1
    30
}
jens@1
    31
jens@1
    32
jens@1
    33
- (void)dealloc
jens@1
    34
{
jens@1
    35
    [_game release];
jens@1
    36
    [super dealloc];
jens@1
    37
}
jens@1
    38
jens@1
    39
jens@1
    40
@synthesize game=_game, gameboard=_gameboard;
jens@1
    41
jens@1
    42
jens@1
    43
- (void) startGameNamed: (NSString*)gameClassName
jens@1
    44
{
jens@4
    45
    Class gameClass = NSClassFromString(gameClassName);
jens@4
    46
    NSAssert1(gameClass,@"Unknown game '%@'",gameClassName);
jens@4
    47
    
jens@4
    48
    setObj(&_game,nil);
jens@1
    49
    if( _gameboard ) {
jens@1
    50
        [_gameboard removeFromSuperlayer];
jens@1
    51
        _gameboard = nil;
jens@1
    52
    }
jens@4
    53
jens@4
    54
    CALayer *rootLayer = self.layer;
jens@4
    55
    self.layer.affineTransform = CGAffineTransformIdentity;
jens@4
    56
    CGRect frame = rootLayer.frame;
jens@4
    57
    frame.origin.x = frame.origin.y = 0;
jens@4
    58
    rootLayer.bounds = frame;
jens@4
    59
jens@4
    60
    if( [gameClass landscapeOriented] && frame.size.height > frame.size.width ) {
jens@4
    61
        rootLayer.affineTransform = CGAffineTransformMakeRotation(M_PI/2);
jens@4
    62
        frame = CGRectMake(0,0,frame.size.height,frame.size.width);
jens@4
    63
        rootLayer.bounds = frame;
jens@4
    64
    }
jens@4
    65
    
jens@1
    66
    _gameboard = [[GGBLayer alloc] init];
jens@4
    67
    _gameboard.frame = frame;
jens@4
    68
    [rootLayer addSublayer: _gameboard];
jens@1
    69
    [_gameboard release];
jens@1
    70
    
jens@4
    71
    _game = [[gameClass alloc] initWithBoard: _gameboard];
jens@1
    72
}
jens@1
    73
jens@1
    74
jens@1
    75
- (CGRect) gameBoardFrame
jens@1
    76
{
jens@1
    77
    return self.layer.bounds;
jens@1
    78
}
jens@1
    79
jens@1
    80
jens@1
    81
#pragma mark -
jens@1
    82
#pragma mark HIT-TESTING:
jens@1
    83
jens@1
    84
jens@1
    85
// Hit-testing callbacks (to identify which layers caller is interested in):
jens@1
    86
typedef BOOL (*LayerMatchCallback)(CALayer*);
jens@1
    87
jens@1
    88
static BOOL layerIsBit( CALayer* layer )        {return [layer isKindOfClass: [Bit class]];}
jens@1
    89
static BOOL layerIsBitHolder( CALayer* layer )  {return [layer conformsToProtocol: @protocol(BitHolder)];}
jens@1
    90
jens@1
    91
jens@1
    92
/** Locates the layer at a given point in window coords.
jens@1
    93
    If the leaf layer doesn't pass the layer-match callback, the nearest ancestor that does is returned.
jens@1
    94
    If outOffset is provided, the point's position relative to the layer is stored into it. */
jens@1
    95
- (CALayer*) hitTestPoint: (CGPoint)where
jens@1
    96
         forLayerMatching: (LayerMatchCallback)match
jens@1
    97
                   offset: (CGPoint*)outOffset
jens@1
    98
{
jens@1
    99
    where = [_gameboard convertPoint: where fromLayer: self.layer];
jens@1
   100
    CALayer *layer = [_gameboard hitTest: where];
jens@1
   101
    while( layer ) {
jens@1
   102
        if( match(layer) ) {
jens@1
   103
            CGPoint bitPos = [self.layer convertPoint: layer.position 
jens@1
   104
                              fromLayer: layer.superlayer];
jens@1
   105
            if( outOffset )
jens@1
   106
                *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y);
jens@1
   107
            return layer;
jens@1
   108
        } else
jens@1
   109
            layer = layer.superlayer;
jens@1
   110
    }
jens@1
   111
    return nil;
jens@1
   112
}
jens@1
   113
jens@1
   114
jens@1
   115
#pragma mark -
jens@1
   116
#pragma mark MOUSE CLICKS & DRAGS:
jens@1
   117
jens@1
   118
jens@1
   119
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
jens@1
   120
{
jens@1
   121
    NSAssert(touches.count==1,@"No multitouch support yet");
jens@1
   122
    UITouch *touch = touches.anyObject;
jens@1
   123
    
jens@3
   124
    BOOL placing = NO;
jens@8
   125
    _dragStartPos = [touch locationInView: self];
jens@1
   126
    _dragBit = (Bit*) [self hitTestPoint: _dragStartPos
jens@1
   127
                        forLayerMatching: layerIsBit 
jens@1
   128
                                  offset: &_dragOffset];
jens@3
   129
jens@3
   130
    if( ! _dragBit ) {
jens@3
   131
        // If no bit was clicked, see if it's a BitHolder the game will let the user add a Bit to:
jens@3
   132
        id<BitHolder> holder = (id<BitHolder>) [self hitTestPoint: _dragStartPos
jens@3
   133
                                                 forLayerMatching: layerIsBitHolder
jens@3
   134
                                                           offset: NULL];
jens@3
   135
        if( holder ) {
jens@3
   136
            _dragBit = [_game bitToPlaceInHolder: holder];
jens@3
   137
            if( _dragBit ) {
jens@3
   138
                _dragOffset.x = _dragOffset.y = 0;
jens@3
   139
                if( _dragBit.superlayer==nil )
jens@3
   140
                    _dragBit.position = _dragStartPos;
jens@3
   141
                placing = YES;
jens@3
   142
            }
jens@3
   143
        }
jens@3
   144
    }
jens@3
   145
jens@3
   146
    if( ! _dragBit ) {
jens@3
   147
        Beep();
jens@3
   148
        return;
jens@3
   149
    }
jens@3
   150
    
jens@3
   151
    // Clicked on a Bit:
jens@3
   152
    _dragMoved = NO;
jens@3
   153
    _dropTarget = nil;
jens@3
   154
    _oldHolder = _dragBit.holder;
jens@3
   155
    // Ask holder's and game's permission before dragging:
jens@3
   156
    if( _oldHolder ) {
jens@3
   157
        _dragBit = [_oldHolder canDragBit: _dragBit];
jens@1
   158
        if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) {
jens@1
   159
            [_oldHolder cancelDragBit: _dragBit];
jens@1
   160
            _dragBit = nil;
jens@1
   161
        }
jens@1
   162
        if( ! _dragBit ) {
jens@1
   163
            _oldHolder = nil;
jens@1
   164
            Beep();
jens@1
   165
            return;
jens@1
   166
        }
jens@3
   167
    }
jens@3
   168
    // Start dragging:
jens@3
   169
    _oldSuperlayer = _dragBit.superlayer;
jens@3
   170
    _oldLayerIndex = [_oldSuperlayer.sublayers indexOfObjectIdenticalTo: _dragBit];
jens@3
   171
    _oldPos = _dragBit.position;
jens@3
   172
    ChangeSuperlayer(_dragBit, self.layer, self.layer.sublayers.count);
jens@3
   173
    _dragBit.pickedUp = YES;
jens@3
   174
    
jens@3
   175
    if( placing ) {
jens@3
   176
        if( _oldSuperlayer )
jens@3
   177
            _dragBit.position = _dragStartPos;      // animate Bit to new position
jens@3
   178
        _dragMoved = YES;
jens@3
   179
        [self _findDropTarget: _dragStartPos];
jens@3
   180
    }
jens@1
   181
}
jens@1
   182
jens@1
   183
jens@1
   184
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
jens@1
   185
{
jens@1
   186
    NSAssert(touches.count==1,@"No multitouch support yet");
jens@1
   187
    UITouch *touch = touches.anyObject;
jens@1
   188
    
jens@1
   189
    if( _dragBit ) {
jens@1
   190
        // Get the mouse position, and see if we've moved 3 pixels since the mouseDown:
jens@8
   191
        CGPoint pos = [touch locationInView: self];
jens@1
   192
        if( fabs(pos.x-_dragStartPos.x)>=3 || fabs(pos.y-_dragStartPos.y)>=3 )
jens@1
   193
            _dragMoved = YES;
jens@1
   194
        
jens@1
   195
        // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness):
jens@1
   196
        pos.x += _dragOffset.x;
jens@1
   197
        pos.y += _dragOffset.y;
jens@1
   198
        
jens@1
   199
        CGPoint newPos = [_dragBit.superlayer convertPoint: pos fromLayer: self.layer];
jens@1
   200
jens@1
   201
        [CATransaction flush];
jens@9
   202
        BeginDisableAnimations();
jens@1
   203
        _dragBit.position = newPos;
jens@9
   204
        EndDisableAnimations();
jens@1
   205
jens@1
   206
        // Find what it's over:
jens@3
   207
        [self _findDropTarget: pos];
jens@3
   208
    }
jens@3
   209
}
jens@3
   210
jens@3
   211
jens@3
   212
- (void) _findDropTarget: (CGPoint)pos
jens@3
   213
{
jens@3
   214
    id<BitHolder> target = (id<BitHolder>) [self hitTestPoint: pos
jens@3
   215
                                             forLayerMatching: layerIsBitHolder
jens@3
   216
                                                       offset: NULL];
jens@3
   217
    if( target == _oldHolder )
jens@3
   218
        target = nil;
jens@3
   219
    if( target != _dropTarget ) {
jens@3
   220
        [_dropTarget willNotDropBit: _dragBit];
jens@3
   221
        _dropTarget.highlighted = NO;
jens@3
   222
        _dropTarget = nil;
jens@3
   223
    }
jens@3
   224
    if( target ) {
jens@3
   225
        CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position
jens@3
   226
                                                 fromLayer: _dragBit.superlayer];
jens@3
   227
        if( [target canDropBit: _dragBit atPoint: targetPos]
jens@3
   228
           && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) {
jens@3
   229
            _dropTarget = target;
jens@3
   230
            _dropTarget.highlighted = YES;
jens@1
   231
        }
jens@1
   232
    }
jens@3
   233
}    
jens@1
   234
jens@1
   235
jens@1
   236
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
jens@1
   237
{
jens@1
   238
    if( _dragBit ) {
jens@1
   239
        if( _dragMoved ) {
jens@1
   240
            // Update the drag tracking to the final mouse position:
jens@1
   241
            [self touchesMoved: touches withEvent: event];
jens@1
   242
            _dropTarget.highlighted = NO;
jens@1
   243
            _dragBit.pickedUp = NO;
jens@1
   244
jens@1
   245
            // Is the move legal?
jens@1
   246
            if( _dropTarget && [_dropTarget dropBit: _dragBit
jens@1
   247
                                            atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position 
jens@1
   248
                                                                            fromLayer: _dragBit.superlayer]] ) {
jens@1
   249
                // Yes, notify the interested parties:
jens@1
   250
                [_oldHolder draggedBit: _dragBit to: _dropTarget];
jens@1
   251
                [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget];
jens@1
   252
            } else {
jens@1
   253
                // Nope, cancel:
jens@1
   254
                [_dropTarget willNotDropBit: _dragBit];
jens@3
   255
                if( _oldSuperlayer ) {
jens@3
   256
                    ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
jens@3
   257
                    _dragBit.position = _oldPos;
jens@3
   258
                    [_oldHolder cancelDragBit: _dragBit];
jens@3
   259
                } else {
jens@3
   260
                    [_dragBit removeFromSuperlayer];
jens@3
   261
                }
jens@9
   262
                Beep();
jens@1
   263
            }
jens@1
   264
        } else {
jens@1
   265
            // Just a click, without a drag:
jens@1
   266
            _dropTarget.highlighted = NO;
jens@1
   267
            _dragBit.pickedUp = NO;
jens@1
   268
            ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
jens@1
   269
            [_oldHolder cancelDragBit: _dragBit];
jens@1
   270
            if( ! [_game clickedBit: _dragBit] )
jens@1
   271
                Beep();
jens@1
   272
        }
jens@1
   273
        _dropTarget = nil;
jens@1
   274
        _dragBit = nil;
jens@1
   275
    }
jens@1
   276
}
jens@1
   277
jens@1
   278
jens@1
   279
@end