Fixed: An exception in the Go game if you mouse down on the board but then drag to the captured-pieces area and release the mouse.
5 // Created by Jens Alfke on 3/7/08.
6 // Copyright 2008 __MyCompanyName__. All rights reserved.
9 #import "BoardUIView.h"
13 #import "QuartzUtils.h"
17 @interface BoardUIView ()
18 - (void) _findDropTarget: (CGPoint)pos;
22 @implementation BoardUIView
25 - (id)initWithFrame:(CGRect)frame {
26 if ( (self = [super initWithFrame:frame]) ) {
27 // Initialization code here.
40 @synthesize game=_game, gameboard=_gameboard;
43 - (void) startGameNamed: (NSString*)gameClassName
45 Class gameClass = NSClassFromString(gameClassName);
46 NSAssert1(gameClass,@"Unknown game '%@'",gameClassName);
50 [_gameboard removeFromSuperlayer];
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;
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;
66 _gameboard = [[GGBLayer alloc] init];
67 _gameboard.frame = frame;
68 [rootLayer addSublayer: _gameboard];
71 _game = [[gameClass alloc] initWithBoard: _gameboard];
75 - (CGRect) gameBoardFrame
77 return self.layer.bounds;
82 #pragma mark HIT-TESTING:
85 // Hit-testing callbacks (to identify which layers caller is interested in):
86 typedef BOOL (*LayerMatchCallback)(CALayer*);
88 static BOOL layerIsBit( CALayer* layer ) {return [layer isKindOfClass: [Bit class]];}
89 static BOOL layerIsBitHolder( CALayer* layer ) {return [layer conformsToProtocol: @protocol(BitHolder)];}
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
99 where = [_gameboard convertPoint: where fromLayer: self.layer];
100 CALayer *layer = [_gameboard hitTest: where];
103 CGPoint bitPos = [self.layer convertPoint: layer.position
104 fromLayer: layer.superlayer];
106 *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y);
109 layer = layer.superlayer;
116 #pragma mark MOUSE CLICKS & DRAGS:
119 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
121 NSAssert(touches.count==1,@"No multitouch support yet");
122 UITouch *touch = touches.anyObject;
125 _dragStartPos = touch.locationInView;
126 _dragBit = (Bit*) [self hitTestPoint: _dragStartPos
127 forLayerMatching: layerIsBit
128 offset: &_dragOffset];
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
136 _dragBit = [_game bitToPlaceInHolder: holder];
138 _dragOffset.x = _dragOffset.y = 0;
139 if( _dragBit.superlayer==nil )
140 _dragBit.position = _dragStartPos;
154 _oldHolder = _dragBit.holder;
155 // Ask holder's and game's permission before dragging:
157 _dragBit = [_oldHolder canDragBit: _dragBit];
158 if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) {
159 [_oldHolder cancelDragBit: _dragBit];
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;
177 _dragBit.position = _dragStartPos; // animate Bit to new position
179 [self _findDropTarget: _dragStartPos];
184 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
186 NSAssert(touches.count==1,@"No multitouch support yet");
187 UITouch *touch = touches.anyObject;
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 )
195 // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness):
196 pos.x += _dragOffset.x;
197 pos.y += _dragOffset.y;
199 CGPoint newPos = [_dragBit.superlayer convertPoint: pos fromLayer: self.layer];
201 [CATransaction flush];
202 [CATransaction begin];
203 [CATransaction setValue:(id)kCFBooleanTrue
204 forKey:kCATransactionDisableActions];
205 _dragBit.position = newPos;
206 [CATransaction commit];
208 // Find what it's over:
209 [self _findDropTarget: pos];
214 - (void) _findDropTarget: (CGPoint)pos
216 id<BitHolder> target = (id<BitHolder>) [self hitTestPoint: pos
217 forLayerMatching: layerIsBitHolder
219 if( target == _oldHolder )
221 if( target != _dropTarget ) {
222 [_dropTarget willNotDropBit: _dragBit];
223 _dropTarget.highlighted = NO;
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;
238 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
242 // Update the drag tracking to the final mouse position:
243 [self touchesMoved: touches withEvent: event];
244 _dropTarget.highlighted = NO;
245 _dragBit.pickedUp = NO;
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];
256 [_dropTarget willNotDropBit: _dragBit];
257 if( _oldSuperlayer ) {
258 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
259 _dragBit.position = _oldPos;
260 [_oldHolder cancelDragBit: _dragBit];
262 [_dragBit removeFromSuperlayer];
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] )