Added ball graphics to Xcode project, which fixes the Go game. Fixed Tic-Tac-Toe. Fixed an exception in the demo app when changing games.
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.
26 #import "Game+Protected.h"
29 #import "QuartzUtils.h"
33 #define kMaxPerspective 0.965 // 55 degrees
36 @interface BoardView ()
37 - (void) _findDropTarget: (NSPoint)pos;
41 @implementation BoardView
44 @synthesize table=_table, gameBoardInset=_gameBoardInset;
55 #pragma mark PERSPECTIVE:
58 - (void) _applyPerspective
61 if( fabs(_perspective) >= M_PI/180 ) {
62 CGSize size = self.layer.bounds.size;
63 t = CATransform3DMakeTranslation(-size.width/2, -size.height/4, 0);
64 t = CATransform3DConcat(t, CATransform3DMakeRotation(-_perspective, 1,0,0));
66 CATransform3D pers = CATransform3DIdentity;
68 t = CATransform3DConcat(t, pers);
69 t = CATransform3DConcat(t, CATransform3DMakeTranslation(size.width/2,
70 size.height*(0.25 + 0.05*sin(2*_perspective)),
72 self.layer.borderWidth = 3;
74 t = CATransform3DIdentity;
75 self.layer.borderWidth = 0;
77 self.layer.transform = t;
80 - (CGFloat) perspective {return _perspective;}
82 - (void) setPerspective: (CGFloat)p
84 p = MAX(0.0, MIN(kMaxPerspective, p));
85 if( p != _perspective ) {
87 [self _applyPerspective];
88 _game.tablePerspectiveAngle = p;
92 - (IBAction) tiltUp: (id)sender {self.perspective -= M_PI/40;}
93 - (IBAction) tiltDown: (id)sender {self.perspective += M_PI/40;}
97 #pragma mark GAME BOARD:
100 - (void) _removeGameBoard
103 RemoveImmediately(_table);
108 - (void) createGameBoard
110 [self _removeGameBoard];
111 _table = [[CALayer alloc] init];
112 _table.frame = [self gameBoardFrame];
113 _table.autoresizingMask = kCALayerMinXMargin | kCALayerMaxXMargin | kCALayerMinYMargin | kCALayerMaxYMargin;
115 // Tell the game to set up the board:
116 _game.tablePerspectiveAngle = _perspective;
117 _game.table = _table;
119 [self.layer addSublayer: _table];
129 - (void) setGame: (Game*)game
134 [self createGameBoard];
138 - (void) startGameNamed: (NSString*)gameClassName
140 Class gameClass = NSClassFromString(gameClassName);
141 Game *game = [[gameClass alloc] init];
150 #pragma mark VIEW MANIPULATION:
153 - (CGRect) gameBoardFrame
155 return CGRectInset(self.layer.bounds, _gameBoardInset.width,_gameBoardInset.height);
159 - (void)resetCursorRects
161 [super resetCursorRects];
163 [self addCursorRect: self.bounds cursor: [NSCursor openHandCursor]];
167 - (NSView*) fullScreenView
169 return _fullScreenView ?: self;
172 - (IBAction) enterFullScreen: (id)sender
174 //[self _removeGameBoard];
175 if( self.fullScreenView.isInFullScreenMode ) {
176 [self.fullScreenView exitFullScreenModeWithOptions: nil];
178 [self.fullScreenView enterFullScreenMode: self.window.screen
181 //[self createGameBoard];
185 - (void)viewWillStartLiveResize
187 [super viewWillStartLiveResize];
188 _oldSize = self.frame.size;
191 - (void)setFrameSize:(NSSize)newSize
193 [super setFrameSize: newSize];
194 if( _oldSize.width > 0.0f ) {
195 CGAffineTransform xform = _table.affineTransform;
196 xform.a = xform.d = MIN(newSize.width,newSize.height)/MIN(_oldSize.width,_oldSize.height);
197 BeginDisableAnimations();
198 [self _applyPerspective];
199 _table.affineTransform = xform;
200 EndDisableAnimations();
202 [self createGameBoard];
205 - (void)viewDidEndLiveResize
207 [super viewDidEndLiveResize];
208 _oldSize.width = _oldSize.height = 0.0f;
209 [self createGameBoard];
214 #pragma mark KEY EVENTS:
217 - (BOOL) performKeyEquivalent: (NSEvent*)ev
219 if( [ev.charactersIgnoringModifiers hasPrefix: @"\033"] ) { // Esc key
220 if( self.fullScreenView.isInFullScreenMode ) {
221 [self performSelector: @selector(enterFullScreen:) withObject: nil afterDelay: 0.0];
222 // without the delayed-perform, NSWindow crashes right after this method returns!
231 #pragma mark HIT-TESTING:
234 /** Converts a point from window coords, to this view's root layer's coords. */
235 - (CGPoint) _convertPointFromWindowToLayer: (NSPoint)locationInWindow
237 NSPoint where = [self convertPoint: locationInWindow fromView: nil]; // convert to view coords
238 where = [self convertPointToBase: where]; // then to layer base coords
239 return [self.layer convertPoint: NSPointToCGPoint(where) // then to transformed layer coords
240 fromLayer: self.layer.superlayer];
244 // Hit-testing callbacks (to identify which layers caller is interested in):
245 typedef BOOL (*LayerMatchCallback)(CALayer*);
247 static BOOL layerIsBit( CALayer* layer ) {return [layer isKindOfClass: [Bit class]];}
248 static BOOL layerIsBitHolder( CALayer* layer ) {return [layer conformsToProtocol: @protocol(BitHolder)];}
249 static BOOL layerIsDropTarget( CALayer* layer ) {return [layer respondsToSelector: @selector(draggingEntered:)];}
252 /** Locates the layer at a given point in window coords.
253 If the leaf layer doesn't pass the layer-match callback, the nearest ancestor that does is returned.
254 If outOffset is provided, the point's position relative to the layer is stored into it. */
255 - (CALayer*) hitTestPoint: (NSPoint)locationInWindow
256 forLayerMatching: (LayerMatchCallback)match
257 offset: (CGPoint*)outOffset
259 CGPoint where = [self _convertPointFromWindowToLayer: locationInWindow ];
260 CALayer *layer = [_table hitTest: where];
263 CGPoint bitPos = [self.layer convertPoint: layer.position
264 fromLayer: layer.superlayer];
266 *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y);
269 layer = layer.superlayer;
276 #pragma mark MOUSE CLICKS & DRAGS:
279 - (void) mouseDown: (NSEvent*)ev
281 if( ! _game.okToMove ) {
287 _dragStartPos = ev.locationInWindow;
288 _dragBit = (Bit*) [self hitTestPoint: _dragStartPos
289 forLayerMatching: layerIsBit
290 offset: &_dragOffset];
293 // If no bit was clicked, see if it's a BitHolder the game will let the user add a Bit to:
294 id<BitHolder> holder = (id<BitHolder>) [self hitTestPoint: _dragStartPos
295 forLayerMatching: layerIsBitHolder
298 _dragBit = [_game bitToPlaceInHolder: holder];
300 _dragOffset.x = _dragOffset.y = 0;
301 if( _dragBit.superlayer==nil )
302 _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
316 _oldHolder = _dragBit.holder;
317 // Ask holder's and game's permission before dragging:
319 _dragBit = [_oldHolder canDragBit: _dragBit];
320 if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) {
321 [_oldHolder cancelDragBit: _dragBit];
332 _oldSuperlayer = _dragBit.superlayer;
333 _oldLayerIndex = [_oldSuperlayer.sublayers indexOfObjectIdenticalTo: _dragBit];
334 _oldPos = _dragBit.position;
335 ChangeSuperlayer(_dragBit, self.layer, self.layer.sublayers.count);
336 _dragBit.pickedUp = YES;
337 [[NSCursor closedHandCursor] push];
341 _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
343 [self _findDropTarget: _dragStartPos];
348 - (void) mouseDragged: (NSEvent*)ev
351 // Get the mouse position, and see if we've moved 3 pixels since the mouseDown:
352 NSPoint pos = ev.locationInWindow;
353 if( fabs(pos.x-_dragStartPos.x)>=3 || fabs(pos.y-_dragStartPos.y)>=3 )
356 // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness):
357 CGPoint where = [self _convertPointFromWindowToLayer: pos];
358 where.x += _dragOffset.x;
359 where.y += _dragOffset.y;
361 CGPoint newPos = [_dragBit.superlayer convertPoint: where fromLayer: self.layer];
363 [CATransaction flush];
364 [CATransaction begin];
365 [CATransaction setValue:(id)kCFBooleanTrue
366 forKey:kCATransactionDisableActions];
367 _dragBit.position = newPos;
368 [CATransaction commit];
370 // Find what it's over:
371 [self _findDropTarget: pos];
376 - (void) _findDropTarget: (NSPoint)locationInWindow
378 locationInWindow.x += _dragOffset.x;
379 locationInWindow.y += _dragOffset.y;
380 id<BitHolder> target = (id<BitHolder>) [self hitTestPoint: locationInWindow
381 forLayerMatching: layerIsBitHolder
383 if( target == _oldHolder )
385 if( target != _dropTarget ) {
386 [_dropTarget willNotDropBit: _dragBit];
387 _dropTarget.highlighted = NO;
391 CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position
392 fromLayer: _dragBit.superlayer];
393 if( [target canDropBit: _dragBit atPoint: targetPos]
394 && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) {
395 _dropTarget = target;
396 _dropTarget.highlighted = YES;
402 - (void) mouseUp: (NSEvent*)ev
406 // Update the drag tracking to the final mouse position:
407 [self mouseDragged: ev];
408 _dropTarget.highlighted = NO;
409 _dragBit.pickedUp = NO;
411 // Is the move legal?
412 if( _dropTarget && [_dropTarget dropBit: _dragBit
413 atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position
414 fromLayer: _dragBit.superlayer]] ) {
415 // Yes, notify the interested parties:
416 [_oldHolder draggedBit: _dragBit to: _dropTarget];
417 [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget];
420 [_dropTarget willNotDropBit: _dragBit];
421 if( _oldSuperlayer ) {
422 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
423 _dragBit.position = _oldPos;
424 [_oldHolder cancelDragBit: _dragBit];
426 [_dragBit removeFromSuperlayer];
430 // Just a click, without a drag:
431 _dropTarget.highlighted = NO;
432 _dragBit.pickedUp = NO;
433 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
434 [_oldHolder cancelDragBit: _dragBit];
435 if( ! [_game clickedBit: _dragBit] )
446 - (void)scrollWheel:(NSEvent *)e
448 self.perspective += e.deltaY * M_PI/180;
449 //Log(@"Perspective = %2.0f degrees (%5.3f radians)", self.perspective*180/M_PI, self.perspective);
454 #pragma mark INCOMING DRAGS:
457 // subroutine to call the target
458 static int tell( id target, SEL selector, id arg, int defaultValue )
460 if( target && [target respondsToSelector: selector] )
461 return (ssize_t) [target performSelector: selector withObject: arg];
467 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
469 _viewDropTarget = [self hitTestPoint: [sender draggingLocation]
470 forLayerMatching: layerIsDropTarget
472 _viewDropOp = _viewDropTarget ?[_viewDropTarget draggingEntered: sender] :NSDragOperationNone;
476 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
478 CALayer *target = [self hitTestPoint: [sender draggingLocation]
479 forLayerMatching: layerIsDropTarget
481 if( target == _viewDropTarget ) {
482 if( _viewDropTarget )
483 _viewDropOp = tell(_viewDropTarget,@selector(draggingUpdated:),sender,_viewDropOp);
485 tell(_viewDropTarget,@selector(draggingExited:),sender,0);
486 _viewDropTarget = target;
487 if( _viewDropTarget )
488 _viewDropOp = [_viewDropTarget draggingEntered: sender];
490 _viewDropOp = NSDragOperationNone;
495 - (BOOL)wantsPeriodicDraggingUpdates
497 return (_viewDropTarget!=nil);
500 - (void)draggingExited:(id <NSDraggingInfo>)sender
502 tell(_viewDropTarget,@selector(draggingExited:),sender,0);
503 _viewDropTarget = nil;
506 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
508 return tell(_viewDropTarget,@selector(prepareForDragOperation:),sender,YES);
511 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
513 return [_viewDropTarget performDragOperation: sender];
516 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
518 tell(_viewDropTarget,@selector(concludeDragOperation:),sender,0);
521 - (void)draggingEnded:(id <NSDraggingInfo>)sender
523 tell(_viewDropTarget,@selector(draggingEnded:),sender,0);