Lots of reworking. Completed support for game history, including Turn class. Changed Game API around quite a bit.
1 /* This code is based on Apple's "GeekGameBoard" sample code, version 1.0.
2 http://developer.apple.com/samplecode/GeekGameBoard/
3 Copyright © 2007 Apple Inc. Copyright © 2008 Jens Alfke. All Rights Reserved.
5 Redistribution and use in source and binary forms, with or without modification, are permitted
6 provided that the following conditions are met:
8 * Redistributions of source code must retain the above copyright notice, this list of conditions
9 and the following disclaimer.
10 * Redistributions in binary form must reproduce the above copyright notice, this list of
11 conditions and the following disclaimer in the documentation and/or other materials provided
12 with the distribution.
14 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
15 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
17 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
19 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
20 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
21 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 #import "QuartzUtils.h"
32 @interface BoardView ()
33 - (void) _findDropTarget: (NSPoint)pos;
37 @implementation BoardView
40 @synthesize gameboard=_gameboard;
50 - (void) _removeGameBoard
53 RemoveImmediately(_gameboard);
58 - (void) createGameBoard
60 [self _removeGameBoard];
61 _gameboard = [[CALayer alloc] init];
62 _gameboard.frame = [self gameBoardFrame];
63 _gameboard.autoresizingMask = kCALayerMinXMargin | kCALayerMaxXMargin | kCALayerMinYMargin | kCALayerMaxYMargin;
65 // Tell the game to set up the board:
66 _game.board = _gameboard;
68 [self.layer addSublayer: _gameboard];
75 [_gameboard setValue: [NSNumber numberWithDouble: M_PI]
76 forKeyPath: @"transform.rotation"];
85 - (void) setGame: (Game*)game
89 [self createGameBoard];
93 - (void) startGameNamed: (NSString*)gameClassName
95 Class gameClass = NSClassFromString(gameClassName);
96 Game *game = [[gameClass alloc] init];
106 return (_game && _game.currentPlayer.local && _game.currentTurnNo==_game.maxTurnNo);
110 - (CGRect) gameBoardFrame
112 return self.layer.bounds;
116 - (void)resetCursorRects
118 [super resetCursorRects];
119 if( self.canMakeMove )
120 [self addCursorRect: self.bounds cursor: [NSCursor openHandCursor]];
124 - (IBAction) enterFullScreen: (id)sender
126 [self _removeGameBoard];
127 if( self.isInFullScreenMode ) {
128 [self exitFullScreenModeWithOptions: nil];
130 [self enterFullScreenMode: self.window.screen
133 [self createGameBoard];
137 - (void)viewWillStartLiveResize
139 [super viewWillStartLiveResize];
140 _oldSize = self.frame.size;
143 - (void)setFrameSize:(NSSize)newSize
145 [super setFrameSize: newSize];
146 if( _oldSize.width > 0.0f ) {
147 CGAffineTransform xform = _gameboard.affineTransform;
148 xform.a = xform.d = MIN(newSize.width,newSize.height)/MIN(_oldSize.width,_oldSize.height);
149 _gameboard.affineTransform = xform;
151 [self createGameBoard];
154 - (void)viewDidEndLiveResize
156 [super viewDidEndLiveResize];
157 _oldSize.width = _oldSize.height = 0.0f;
158 [self createGameBoard];
163 #pragma mark KEY EVENTS:
166 - (void) keyDown: (NSEvent*)ev
168 if( self.isInFullScreenMode ) {
169 if( [ev.charactersIgnoringModifiers hasPrefix: @"\033"] ) // Esc key
170 [self enterFullScreen: self];
176 #pragma mark HIT-TESTING:
179 /** Converts a point from window coords, to this view's root layer's coords. */
180 - (CGPoint) _convertPointFromWindowToLayer: (NSPoint)locationInWindow
182 NSPoint where = [self convertPoint: locationInWindow fromView: nil]; // convert to view coords
183 return NSPointToCGPoint( [self convertPointToBase: where] ); // then to layer coords
187 // Hit-testing callbacks (to identify which layers caller is interested in):
188 typedef BOOL (*LayerMatchCallback)(CALayer*);
190 static BOOL layerIsBit( CALayer* layer ) {return [layer isKindOfClass: [Bit class]];}
191 static BOOL layerIsBitHolder( CALayer* layer ) {return [layer conformsToProtocol: @protocol(BitHolder)];}
192 static BOOL layerIsDropTarget( CALayer* layer ) {return [layer respondsToSelector: @selector(draggingEntered:)];}
195 /** Locates the layer at a given point in window coords.
196 If the leaf layer doesn't pass the layer-match callback, the nearest ancestor that does is returned.
197 If outOffset is provided, the point's position relative to the layer is stored into it. */
198 - (CALayer*) hitTestPoint: (NSPoint)locationInWindow
199 forLayerMatching: (LayerMatchCallback)match
200 offset: (CGPoint*)outOffset
202 CGPoint where = [self _convertPointFromWindowToLayer: locationInWindow ];
203 CALayer *layer = [_gameboard hitTest: where];
206 CGPoint bitPos = [self.layer convertPoint: layer.position
207 fromLayer: layer.superlayer];
209 *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y);
212 layer = layer.superlayer;
219 #pragma mark MOUSE CLICKS & DRAGS:
222 - (void) mouseDown: (NSEvent*)ev
224 if( ! self.canMakeMove ) {
230 _dragStartPos = ev.locationInWindow;
231 _dragBit = (Bit*) [self hitTestPoint: _dragStartPos
232 forLayerMatching: layerIsBit
233 offset: &_dragOffset];
236 // If no bit was clicked, see if it's a BitHolder the game will let the user add a Bit to:
237 id<BitHolder> holder = (id<BitHolder>) [self hitTestPoint: _dragStartPos
238 forLayerMatching: layerIsBitHolder
241 _dragBit = [_game bitToPlaceInHolder: holder];
243 _dragOffset.x = _dragOffset.y = 0;
244 if( _dragBit.superlayer==nil )
245 _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
259 _oldHolder = _dragBit.holder;
260 // Ask holder's and game's permission before dragging:
262 _dragBit = [_oldHolder canDragBit: _dragBit];
263 if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) {
264 [_oldHolder cancelDragBit: _dragBit];
275 _oldSuperlayer = _dragBit.superlayer;
276 _oldLayerIndex = [_oldSuperlayer.sublayers indexOfObjectIdenticalTo: _dragBit];
277 _oldPos = _dragBit.position;
278 ChangeSuperlayer(_dragBit, self.layer, self.layer.sublayers.count);
279 _dragBit.pickedUp = YES;
280 [[NSCursor closedHandCursor] push];
284 _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
286 [self _findDropTarget: _dragStartPos];
291 - (void) mouseDragged: (NSEvent*)ev
294 // Get the mouse position, and see if we've moved 3 pixels since the mouseDown:
295 NSPoint pos = ev.locationInWindow;
296 if( fabs(pos.x-_dragStartPos.x)>=3 || fabs(pos.y-_dragStartPos.y)>=3 )
299 // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness):
300 CGPoint where = [self _convertPointFromWindowToLayer: pos];
301 where.x += _dragOffset.x;
302 where.y += _dragOffset.y;
304 CGPoint newPos = [_dragBit.superlayer convertPoint: where fromLayer: self.layer];
306 [CATransaction flush];
307 [CATransaction begin];
308 [CATransaction setValue:(id)kCFBooleanTrue
309 forKey:kCATransactionDisableActions];
310 _dragBit.position = newPos;
311 [CATransaction commit];
313 // Find what it's over:
314 [self _findDropTarget: pos];
319 - (void) _findDropTarget: (NSPoint)locationInWindow
321 locationInWindow.x += _dragOffset.x;
322 locationInWindow.y += _dragOffset.y;
323 id<BitHolder> target = (id<BitHolder>) [self hitTestPoint: locationInWindow
324 forLayerMatching: layerIsBitHolder
326 if( target == _oldHolder )
328 if( target != _dropTarget ) {
329 [_dropTarget willNotDropBit: _dragBit];
330 _dropTarget.highlighted = NO;
334 CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position
335 fromLayer: _dragBit.superlayer];
336 if( [target canDropBit: _dragBit atPoint: targetPos]
337 && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) {
338 _dropTarget = target;
339 _dropTarget.highlighted = YES;
345 - (void) mouseUp: (NSEvent*)ev
349 // Update the drag tracking to the final mouse position:
350 [self mouseDragged: ev];
351 _dropTarget.highlighted = NO;
352 _dragBit.pickedUp = NO;
354 // Is the move legal?
355 if( _dropTarget && [_dropTarget dropBit: _dragBit
356 atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position
357 fromLayer: _dragBit.superlayer]] ) {
358 // Yes, notify the interested parties:
359 [_oldHolder draggedBit: _dragBit to: _dropTarget];
360 [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget];
363 [_dropTarget willNotDropBit: _dragBit];
364 if( _oldSuperlayer ) {
365 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
366 _dragBit.position = _oldPos;
367 [_oldHolder cancelDragBit: _dragBit];
369 [_dragBit removeFromSuperlayer];
373 // Just a click, without a drag:
374 _dropTarget.highlighted = NO;
375 _dragBit.pickedUp = NO;
376 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
377 [_oldHolder cancelDragBit: _dragBit];
378 if( ! [_game clickedBit: _dragBit] )
390 #pragma mark INCOMING DRAGS:
393 // subroutine to call the target
394 static int tell( id target, SEL selector, id arg, int defaultValue )
396 if( target && [target respondsToSelector: selector] )
397 return (ssize_t) [target performSelector: selector withObject: arg];
403 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
405 _viewDropTarget = [self hitTestPoint: [sender draggingLocation]
406 forLayerMatching: layerIsDropTarget
408 _viewDropOp = _viewDropTarget ?[_viewDropTarget draggingEntered: sender] :NSDragOperationNone;
412 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
414 CALayer *target = [self hitTestPoint: [sender draggingLocation]
415 forLayerMatching: layerIsDropTarget
417 if( target == _viewDropTarget ) {
418 if( _viewDropTarget )
419 _viewDropOp = tell(_viewDropTarget,@selector(draggingUpdated:),sender,_viewDropOp);
421 tell(_viewDropTarget,@selector(draggingExited:),sender,0);
422 _viewDropTarget = target;
423 if( _viewDropTarget )
424 _viewDropOp = [_viewDropTarget draggingEntered: sender];
426 _viewDropOp = NSDragOperationNone;
431 - (BOOL)wantsPeriodicDraggingUpdates
433 return (_viewDropTarget!=nil);
436 - (void)draggingExited:(id <NSDraggingInfo>)sender
438 tell(_viewDropTarget,@selector(draggingExited:),sender,0);
439 _viewDropTarget = nil;
442 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
444 return tell(_viewDropTarget,@selector(prepareForDragOperation:),sender,YES);
447 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
449 return [_viewDropTarget performDragOperation: sender];
452 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
454 tell(_viewDropTarget,@selector(concludeDragOperation:),sender,0);
457 - (void)draggingEnded:(id <NSDraggingInfo>)sender
459 tell(_viewDropTarget,@selector(draggingEnded:),sender,0);