Source/BoardUIView.m
author Jens Alfke <jens@mooseyard.com>
Sun Mar 16 15:06:47 2008 -0700 (2008-03-16)
changeset 7 428a194e3e59
parent 3 40d225cf9c43
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!)
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@1
   125
    _dragStartPos = touch.locationInView;
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@1
   191
        CGPoint pos = touch.locationInView;
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@1
   202
        [CATransaction begin];
jens@1
   203
        [CATransaction setValue:(id)kCFBooleanTrue
jens@1
   204
                         forKey:kCATransactionDisableActions];
jens@1
   205
        _dragBit.position = newPos;
jens@1
   206
        [CATransaction commit];
jens@1
   207
jens@1
   208
        // Find what it's over:
jens@3
   209
        [self _findDropTarget: pos];
jens@3
   210
    }
jens@3
   211
}
jens@3
   212
jens@3
   213
jens@3
   214
- (void) _findDropTarget: (CGPoint)pos
jens@3
   215
{
jens@3
   216
    id<BitHolder> target = (id<BitHolder>) [self hitTestPoint: pos
jens@3
   217
                                             forLayerMatching: layerIsBitHolder
jens@3
   218
                                                       offset: NULL];
jens@3
   219
    if( target == _oldHolder )
jens@3
   220
        target = nil;
jens@3
   221
    if( target != _dropTarget ) {
jens@3
   222
        [_dropTarget willNotDropBit: _dragBit];
jens@3
   223
        _dropTarget.highlighted = NO;
jens@3
   224
        _dropTarget = nil;
jens@3
   225
    }
jens@3
   226
    if( target ) {
jens@3
   227
        CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position
jens@3
   228
                                                 fromLayer: _dragBit.superlayer];
jens@3
   229
        if( [target canDropBit: _dragBit atPoint: targetPos]
jens@3
   230
           && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) {
jens@3
   231
            _dropTarget = target;
jens@3
   232
            _dropTarget.highlighted = YES;
jens@1
   233
        }
jens@1
   234
    }
jens@3
   235
}    
jens@1
   236
jens@1
   237
jens@1
   238
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
jens@1
   239
{
jens@1
   240
    if( _dragBit ) {
jens@1
   241
        if( _dragMoved ) {
jens@1
   242
            // Update the drag tracking to the final mouse position:
jens@1
   243
            [self touchesMoved: touches withEvent: event];
jens@1
   244
            _dropTarget.highlighted = NO;
jens@1
   245
            _dragBit.pickedUp = NO;
jens@1
   246
jens@1
   247
            // Is the move legal?
jens@1
   248
            if( _dropTarget && [_dropTarget dropBit: _dragBit
jens@1
   249
                                            atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position 
jens@1
   250
                                                                            fromLayer: _dragBit.superlayer]] ) {
jens@1
   251
                // Yes, notify the interested parties:
jens@1
   252
                [_oldHolder draggedBit: _dragBit to: _dropTarget];
jens@1
   253
                [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget];
jens@1
   254
            } else {
jens@1
   255
                // Nope, cancel:
jens@1
   256
                [_dropTarget willNotDropBit: _dragBit];
jens@3
   257
                if( _oldSuperlayer ) {
jens@3
   258
                    ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
jens@3
   259
                    _dragBit.position = _oldPos;
jens@3
   260
                    [_oldHolder cancelDragBit: _dragBit];
jens@3
   261
                } else {
jens@3
   262
                    [_dragBit removeFromSuperlayer];
jens@3
   263
                }
jens@1
   264
            }
jens@1
   265
        } else {
jens@1
   266
            // Just a click, without a drag:
jens@1
   267
            _dropTarget.highlighted = NO;
jens@1
   268
            _dragBit.pickedUp = NO;
jens@1
   269
            ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
jens@1
   270
            [_oldHolder cancelDragBit: _dragBit];
jens@1
   271
            if( ! [_game clickedBit: _dragBit] )
jens@1
   272
                Beep();
jens@1
   273
        }
jens@1
   274
        _dropTarget = nil;
jens@1
   275
        _dragBit = nil;
jens@1
   276
    }
jens@1
   277
}
jens@1
   278
jens@1
   279
jens@1
   280
@end