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