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