jens@1: // jens@1: // BoardUIView.m jens@1: // GeekGameBoard jens@1: // jens@1: // Created by Jens Alfke on 3/7/08. jens@1: // Copyright 2008 __MyCompanyName__. All rights reserved. jens@1: // jens@1: jens@1: #import "BoardUIView.h" jens@1: #import "Bit.h" jens@1: #import "BitHolder.h" jens@1: #import "Game.h" jens@1: #import "QuartzUtils.h" jens@1: #import "GGBUtils.h" jens@1: jens@1: jens@3: @interface BoardUIView () jens@3: - (void) _findDropTarget: (CGPoint)pos; jens@3: @end jens@3: jens@3: jens@1: @implementation BoardUIView jens@1: jens@1: jens@1: - (id)initWithFrame:(CGRect)frame { jens@1: if ( (self = [super initWithFrame:frame]) ) { jens@1: // Initialization code here. jens@1: } jens@1: return self; jens@1: } jens@1: jens@1: jens@1: - (void)dealloc jens@1: { jens@1: [_game release]; jens@1: [super dealloc]; jens@1: } jens@1: jens@1: jens@1: @synthesize game=_game, gameboard=_gameboard; jens@1: jens@1: jens@1: - (void) startGameNamed: (NSString*)gameClassName jens@1: { jens@4: Class gameClass = NSClassFromString(gameClassName); jens@4: NSAssert1(gameClass,@"Unknown game '%@'",gameClassName); jens@4: jens@4: setObj(&_game,nil); jens@1: if( _gameboard ) { jens@1: [_gameboard removeFromSuperlayer]; jens@1: _gameboard = nil; jens@1: } jens@4: jens@4: CALayer *rootLayer = self.layer; jens@4: self.layer.affineTransform = CGAffineTransformIdentity; jens@4: CGRect frame = rootLayer.frame; jens@4: frame.origin.x = frame.origin.y = 0; jens@4: rootLayer.bounds = frame; jens@4: jens@4: if( [gameClass landscapeOriented] && frame.size.height > frame.size.width ) { jens@4: rootLayer.affineTransform = CGAffineTransformMakeRotation(M_PI/2); jens@4: frame = CGRectMake(0,0,frame.size.height,frame.size.width); jens@4: rootLayer.bounds = frame; jens@4: } jens@4: jens@1: _gameboard = [[GGBLayer alloc] init]; jens@4: _gameboard.frame = frame; jens@4: [rootLayer addSublayer: _gameboard]; jens@1: [_gameboard release]; jens@1: jens@18: _game = [[gameClass alloc] initNewGameWithTable: _gameboard]; jens@1: } jens@1: jens@1: jens@1: - (CGRect) gameBoardFrame jens@1: { jens@1: return self.layer.bounds; jens@1: } jens@1: jens@1: jens@1: #pragma mark - jens@1: #pragma mark HIT-TESTING: jens@1: jens@1: jens@1: // Hit-testing callbacks (to identify which layers caller is interested in): jens@1: typedef BOOL (*LayerMatchCallback)(CALayer*); jens@1: jens@1: static BOOL layerIsBit( CALayer* layer ) {return [layer isKindOfClass: [Bit class]];} jens@1: static BOOL layerIsBitHolder( CALayer* layer ) {return [layer conformsToProtocol: @protocol(BitHolder)];} jens@1: jens@1: jens@1: /** Locates the layer at a given point in window coords. jens@1: If the leaf layer doesn't pass the layer-match callback, the nearest ancestor that does is returned. jens@1: If outOffset is provided, the point's position relative to the layer is stored into it. */ jens@1: - (CALayer*) hitTestPoint: (CGPoint)where jens@1: forLayerMatching: (LayerMatchCallback)match jens@1: offset: (CGPoint*)outOffset jens@1: { jens@1: where = [_gameboard convertPoint: where fromLayer: self.layer]; jens@1: CALayer *layer = [_gameboard hitTest: where]; jens@1: while( layer ) { jens@1: if( match(layer) ) { jens@1: CGPoint bitPos = [self.layer convertPoint: layer.position jens@1: fromLayer: layer.superlayer]; jens@1: if( outOffset ) jens@1: *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y); jens@1: return layer; jens@1: } else jens@1: layer = layer.superlayer; jens@1: } jens@1: return nil; jens@1: } jens@1: jens@1: jens@1: #pragma mark - jens@1: #pragma mark MOUSE CLICKS & DRAGS: jens@1: jens@1: jens@1: - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event jens@1: { jens@1: NSAssert(touches.count==1,@"No multitouch support yet"); jens@1: UITouch *touch = touches.anyObject; jens@1: jens@3: BOOL placing = NO; jens@8: _dragStartPos = [touch locationInView: self]; jens@1: _dragBit = (Bit*) [self hitTestPoint: _dragStartPos jens@1: forLayerMatching: layerIsBit jens@1: offset: &_dragOffset]; jens@3: jens@3: if( ! _dragBit ) { jens@3: // If no bit was clicked, see if it's a BitHolder the game will let the user add a Bit to: jens@3: id holder = (id) [self hitTestPoint: _dragStartPos jens@3: forLayerMatching: layerIsBitHolder jens@3: offset: NULL]; jens@3: if( holder ) { jens@3: _dragBit = [_game bitToPlaceInHolder: holder]; jens@3: if( _dragBit ) { jens@3: _dragOffset.x = _dragOffset.y = 0; jens@3: if( _dragBit.superlayer==nil ) jens@3: _dragBit.position = _dragStartPos; jens@3: placing = YES; jens@3: } jens@3: } jens@3: } jens@3: jens@3: if( ! _dragBit ) { jens@3: Beep(); jens@3: return; jens@3: } jens@3: jens@3: // Clicked on a Bit: jens@3: _dragMoved = NO; jens@3: _dropTarget = nil; jens@3: _oldHolder = _dragBit.holder; jens@3: // Ask holder's and game's permission before dragging: jens@3: if( _oldHolder ) { jens@3: _dragBit = [_oldHolder canDragBit: _dragBit]; jens@1: if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) { jens@1: [_oldHolder cancelDragBit: _dragBit]; jens@1: _dragBit = nil; jens@1: } jens@1: if( ! _dragBit ) { jens@1: _oldHolder = nil; jens@1: Beep(); jens@1: return; jens@1: } jens@3: } jens@3: // Start dragging: jens@3: _oldSuperlayer = _dragBit.superlayer; jens@3: _oldLayerIndex = [_oldSuperlayer.sublayers indexOfObjectIdenticalTo: _dragBit]; jens@3: _oldPos = _dragBit.position; jens@3: ChangeSuperlayer(_dragBit, self.layer, self.layer.sublayers.count); jens@3: _dragBit.pickedUp = YES; jens@3: jens@3: if( placing ) { jens@3: if( _oldSuperlayer ) jens@3: _dragBit.position = _dragStartPos; // animate Bit to new position jens@3: _dragMoved = YES; jens@3: [self _findDropTarget: _dragStartPos]; jens@3: } jens@1: } jens@1: jens@1: jens@1: - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event jens@1: { jens@1: NSAssert(touches.count==1,@"No multitouch support yet"); jens@1: UITouch *touch = touches.anyObject; jens@1: jens@1: if( _dragBit ) { jens@1: // Get the mouse position, and see if we've moved 3 pixels since the mouseDown: jens@8: CGPoint pos = [touch locationInView: self]; jens@1: if( fabs(pos.x-_dragStartPos.x)>=3 || fabs(pos.y-_dragStartPos.y)>=3 ) jens@1: _dragMoved = YES; jens@1: jens@1: // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness): jens@1: pos.x += _dragOffset.x; jens@1: pos.y += _dragOffset.y; jens@1: jens@1: CGPoint newPos = [_dragBit.superlayer convertPoint: pos fromLayer: self.layer]; jens@1: jens@1: [CATransaction flush]; jens@9: BeginDisableAnimations(); jens@1: _dragBit.position = newPos; jens@9: EndDisableAnimations(); jens@1: jens@1: // Find what it's over: jens@3: [self _findDropTarget: pos]; jens@3: } jens@3: } jens@3: jens@3: jens@3: - (void) _findDropTarget: (CGPoint)pos jens@3: { jens@3: id target = (id) [self hitTestPoint: pos jens@3: forLayerMatching: layerIsBitHolder jens@3: offset: NULL]; jens@3: if( target == _oldHolder ) jens@3: target = nil; jens@3: if( target != _dropTarget ) { jens@3: [_dropTarget willNotDropBit: _dragBit]; jens@3: _dropTarget.highlighted = NO; jens@3: _dropTarget = nil; jens@3: } jens@3: if( target ) { jens@3: CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position jens@3: fromLayer: _dragBit.superlayer]; jens@3: if( [target canDropBit: _dragBit atPoint: targetPos] jens@3: && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) { jens@3: _dropTarget = target; jens@3: _dropTarget.highlighted = YES; jens@1: } jens@1: } jens@3: } jens@1: jens@1: jens@1: - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event jens@1: { jens@1: if( _dragBit ) { jens@1: if( _dragMoved ) { jens@1: // Update the drag tracking to the final mouse position: jens@1: [self touchesMoved: touches withEvent: event]; jens@1: _dropTarget.highlighted = NO; jens@1: _dragBit.pickedUp = NO; jens@1: jens@1: // Is the move legal? jens@1: if( _dropTarget && [_dropTarget dropBit: _dragBit jens@1: atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position jens@1: fromLayer: _dragBit.superlayer]] ) { jens@1: // Yes, notify the interested parties: jens@1: [_oldHolder draggedBit: _dragBit to: _dropTarget]; jens@1: [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget]; jens@1: } else { jens@1: // Nope, cancel: jens@1: [_dropTarget willNotDropBit: _dragBit]; jens@3: if( _oldSuperlayer ) { jens@3: ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex); jens@3: _dragBit.position = _oldPos; jens@3: [_oldHolder cancelDragBit: _dragBit]; jens@3: } else { jens@3: [_dragBit removeFromSuperlayer]; jens@3: } jens@9: Beep(); jens@1: } jens@1: } else { jens@1: // Just a click, without a drag: jens@1: _dropTarget.highlighted = NO; jens@1: _dragBit.pickedUp = NO; jens@1: ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex); jens@1: [_oldHolder cancelDragBit: _dragBit]; jens@1: if( ! [_game clickedBit: _dragBit] ) jens@1: Beep(); jens@1: } jens@1: _dropTarget = nil; jens@1: _dragBit = nil; jens@1: } jens@1: } jens@1: jens@1: jens@1: @end