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!)
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] )