Source/BoardUIView.m
author Jens Alfke <jens@mooseyard.com>
Mon Mar 10 17:30:57 2008 -0700 (2008-03-10)
changeset 1 3eb7be1dd7b6
child 3 40d225cf9c43
permissions -rw-r--r--
Tic-tac-toe works on the iPhone simulator!
     1 //
     2 //  BoardUIView.m
     3 //  GeekGameBoard
     4 //
     5 //  Created by Jens Alfke on 3/7/08.
     6 //  Copyright 2008 __MyCompanyName__. All rights reserved.
     7 //
     8 
     9 #import "BoardUIView.h"
    10 #import "Bit.h"
    11 #import "BitHolder.h"
    12 #import "Game.h"
    13 #import "QuartzUtils.h"
    14 #import "GGBUtils.h"
    15 
    16 
    17 @implementation BoardUIView
    18 
    19 
    20 - (id)initWithFrame:(CGRect)frame {
    21     if ( (self = [super initWithFrame:frame]) ) {
    22         // Initialization code here.
    23     }
    24     return self;
    25 }
    26 
    27 
    28 - (void)dealloc
    29 {
    30     [_game release];
    31     [super dealloc];
    32 }
    33 
    34 
    35 @synthesize game=_game, gameboard=_gameboard;
    36 
    37 
    38 - (void) startGameNamed: (NSString*)gameClassName
    39 {
    40     if( _gameboard ) {
    41         [_gameboard removeFromSuperlayer];
    42         _gameboard = nil;
    43     }
    44     _gameboard = [[GGBLayer alloc] init];
    45     _gameboard.frame = [self gameBoardFrame];
    46     _gameboard.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
    47     [self.layer addSublayer: _gameboard];
    48     [_gameboard release];
    49     
    50     Class gameClass = NSClassFromString(gameClassName);
    51     NSAssert1(gameClass,@"Unknown game '%@'",gameClassName);
    52     setObj(&_game, [[gameClass alloc] initWithBoard: _gameboard]);
    53 }
    54 
    55 
    56 - (CGRect) gameBoardFrame
    57 {
    58     return self.layer.bounds;
    59 }
    60 
    61 
    62 #pragma mark -
    63 #pragma mark HIT-TESTING:
    64 
    65 
    66 // Hit-testing callbacks (to identify which layers caller is interested in):
    67 typedef BOOL (*LayerMatchCallback)(CALayer*);
    68 
    69 static BOOL layerIsBit( CALayer* layer )        {return [layer isKindOfClass: [Bit class]];}
    70 static BOOL layerIsBitHolder( CALayer* layer )  {return [layer conformsToProtocol: @protocol(BitHolder)];}
    71 
    72 
    73 /** Locates the layer at a given point in window coords.
    74     If the leaf layer doesn't pass the layer-match callback, the nearest ancestor that does is returned.
    75     If outOffset is provided, the point's position relative to the layer is stored into it. */
    76 - (CALayer*) hitTestPoint: (CGPoint)where
    77          forLayerMatching: (LayerMatchCallback)match
    78                    offset: (CGPoint*)outOffset
    79 {
    80     where = [_gameboard convertPoint: where fromLayer: self.layer];
    81     CALayer *layer = [_gameboard hitTest: where];
    82     while( layer ) {
    83         if( match(layer) ) {
    84             CGPoint bitPos = [self.layer convertPoint: layer.position 
    85                               fromLayer: layer.superlayer];
    86             if( outOffset )
    87                 *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y);
    88             return layer;
    89         } else
    90             layer = layer.superlayer;
    91     }
    92     return nil;
    93 }
    94 
    95 
    96 #pragma mark -
    97 #pragma mark MOUSE CLICKS & DRAGS:
    98 
    99 
   100 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
   101 {
   102     NSAssert(touches.count==1,@"No multitouch support yet");
   103     UITouch *touch = touches.anyObject;
   104     
   105     _dragStartPos = touch.locationInView;
   106     _dragBit = (Bit*) [self hitTestPoint: _dragStartPos
   107                         forLayerMatching: layerIsBit 
   108                                   offset: &_dragOffset];
   109     if( _dragBit ) {
   110         _dragMoved = NO;
   111         _dropTarget = nil;
   112         _oldHolder = _dragBit.holder;
   113         // Ask holder's and game's permission before dragging:
   114         if( _oldHolder )
   115             _dragBit = [_oldHolder canDragBit: _dragBit];
   116         if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) {
   117             [_oldHolder cancelDragBit: _dragBit];
   118             _dragBit = nil;
   119         }
   120         if( ! _dragBit ) {
   121             _oldHolder = nil;
   122             Beep();
   123             return;
   124         }
   125         // Start dragging:
   126         _oldSuperlayer = _dragBit.superlayer;
   127         _oldLayerIndex = [_oldSuperlayer.sublayers indexOfObjectIdenticalTo: _dragBit];
   128         _oldPos = _dragBit.position;
   129         ChangeSuperlayer(_dragBit, self.layer, self.layer.sublayers.count);
   130         _dragBit.pickedUp = YES;
   131     } else
   132         Beep();
   133 }
   134 
   135 
   136 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
   137 {
   138     NSAssert(touches.count==1,@"No multitouch support yet");
   139     UITouch *touch = touches.anyObject;
   140     
   141     if( _dragBit ) {
   142         // Get the mouse position, and see if we've moved 3 pixels since the mouseDown:
   143         CGPoint pos = touch.locationInView;
   144         if( fabs(pos.x-_dragStartPos.x)>=3 || fabs(pos.y-_dragStartPos.y)>=3 )
   145             _dragMoved = YES;
   146         
   147         // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness):
   148         pos.x += _dragOffset.x;
   149         pos.y += _dragOffset.y;
   150         
   151         CGPoint newPos = [_dragBit.superlayer convertPoint: pos fromLayer: self.layer];
   152 
   153         [CATransaction flush];
   154         [CATransaction begin];
   155         [CATransaction setValue:(id)kCFBooleanTrue
   156                          forKey:kCATransactionDisableActions];
   157         _dragBit.position = newPos;
   158         [CATransaction commit];
   159 
   160         // Find what it's over:
   161         id<BitHolder> target = (id<BitHolder>) [self hitTestPoint: pos
   162                                                  forLayerMatching: layerIsBitHolder
   163                                                            offset: NULL];
   164         if( target == _oldHolder )
   165             target = nil;
   166         if( target != _dropTarget ) {
   167             [_dropTarget willNotDropBit: _dragBit];
   168             _dropTarget.highlighted = NO;
   169             _dropTarget = nil;
   170         }
   171         if( target ) {
   172             CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position
   173                                                      fromLayer: _dragBit.superlayer];
   174             if( [target canDropBit: _dragBit atPoint: targetPos]
   175                && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) {
   176                 _dropTarget = target;
   177                 _dropTarget.highlighted = YES;
   178             }
   179         }
   180     }
   181 }
   182 
   183 
   184 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
   185 {
   186     if( _dragBit ) {
   187         if( _dragMoved ) {
   188             // Update the drag tracking to the final mouse position:
   189             [self touchesMoved: touches withEvent: event];
   190             _dropTarget.highlighted = NO;
   191             _dragBit.pickedUp = NO;
   192 
   193             // Is the move legal?
   194             if( _dropTarget && [_dropTarget dropBit: _dragBit
   195                                             atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position 
   196                                                                             fromLayer: _dragBit.superlayer]] ) {
   197                 // Yes, notify the interested parties:
   198                 [_oldHolder draggedBit: _dragBit to: _dropTarget];
   199                 [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget];
   200             } else {
   201                 // Nope, cancel:
   202                 [_dropTarget willNotDropBit: _dragBit];
   203                 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
   204                 _dragBit.position = _oldPos;
   205                 [_oldHolder cancelDragBit: _dragBit];
   206             }
   207         } else {
   208             // Just a click, without a drag:
   209             _dropTarget.highlighted = NO;
   210             _dragBit.pickedUp = NO;
   211             ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
   212             [_oldHolder cancelDragBit: _dragBit];
   213             if( ! [_game clickedBit: _dragBit] )
   214                 Beep();
   215         }
   216         _dropTarget = nil;
   217         _dragBit = nil;
   218     }
   219 }
   220 
   221 
   222 @end