Source/HexGrid.m
author Jens Alfke <jens@mooseyard.com>
Sat Jul 05 17:46:43 2008 -0700 (2008-07-05)
changeset 11 436cbdf56810
parent 0 e9f7ba4718e1
child 16 28392c9a969f
permissions -rw-r--r--
* Improved drag-and-drop (supports CandyBar)
* Fixed DiscPiece
* Inheritable layer styles
etc.
     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.
     4 
     5     Redistribution and use in source and binary forms, with or without modification, are permitted
     6     provided that the following conditions are met:
     7 
     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.
    13 
    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.
    22 */
    23 #import "HexGrid.h"
    24 #import "Game.h"
    25 
    26 
    27 @implementation HexGrid
    28 
    29 
    30 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
    31             spacing: (CGSize)spacing
    32            position: (CGPoint)pos
    33 {
    34     // Ignore given spacing.height; set it to make the hexagons regular.
    35     CGFloat capHeight = spacing.height / 2 * tan(M_PI/6);
    36     CGFloat side = spacing.height / 2 / cos(M_PI/6);
    37     spacing.height = side + capHeight;
    38     
    39     self = [super initWithRows: nRows columns: nColumns
    40                        spacing: spacing
    41                       position: pos];
    42     if( self ) {
    43         _capHeight = capHeight;
    44         _side = side;
    45         self.bounds = CGRectMake(-1, -1, 
    46                                  (nColumns+0.5)*spacing.width + 2,
    47                                  nRows*spacing.height+capHeight + 2);
    48         _cellClass = [Hex class];
    49     }
    50     return self;
    51 }
    52 
    53 
    54 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
    55               frame: (CGRect)frame;
    56 {
    57     // Compute the horizontal spacing:
    58     CGFloat s = floor(MIN( (frame.size.width -2.0)/nColumns,
    59                          (frame.size.height-2.0)/(nRows+0.5*tan(M_PI/6)) / (0.5*(tan(M_PI/6)+1/cos(M_PI/6))) ));
    60     self = [self initWithRows: nRows columns: nColumns
    61                       spacing: CGSizeMake(s,s)
    62                      position: frame.origin];
    63     if( self ) {
    64         // Center in frame:
    65         CGRect curFrame = self.frame;
    66         curFrame.origin.x = round( curFrame.origin.x + (frame.size.width - curFrame.size.width )/2.0f );
    67         curFrame.origin.y = round( curFrame.origin.y + (frame.size.height- curFrame.size.height)/2.0f );
    68         self.frame = curFrame;
    69     }
    70     return self;
    71 }
    72     
    73     
    74 - (void) dealloc
    75 {
    76     CGPathRelease(_cellPath);
    77     [super dealloc];
    78 }
    79 
    80 
    81 - (void) addCellsInHexagon
    82 {
    83     int size = _nRows - !(_nRows & 1);      // make it odd
    84     for( int row=0; row<_nRows; row++ ) {
    85         int n;                              // # of hexes remaining in this row
    86         if( row < size )
    87             n = size - abs(row-size/2);
    88         else
    89             n = 0;
    90         int c0 = floor(((int)_nRows+1-n -(row&1))/2.0);       // col of 1st remaining hex
    91 
    92         for( int col=0; col<_nColumns; col++ )
    93             if( col>=c0 && col<c0+n )
    94                 [self addCellAtRow: row column: col];
    95     }
    96 }
    97 
    98 
    99 - (GridCell*) createCellAtRow: (unsigned)row column: (unsigned)col 
   100                suggestedFrame: (CGRect)frame
   101 {
   102     // Overridden to stagger the odd-numbered rows
   103     if( row & 1 )
   104         frame.origin.x += _spacing.width/2;
   105     frame.size.height += _capHeight;
   106     return [super createCellAtRow: row column: col suggestedFrame: frame];
   107 }
   108 
   109 
   110 // Returns a hexagonal CG path defining a cell's outline. Used by cells when drawing & hit-testing.
   111 - (CGPathRef) cellPath
   112 {
   113     if( ! _cellPath ) {
   114         CGFloat x1 = _spacing.width/2;
   115         CGFloat x2 = _spacing.width;
   116         CGFloat y1 = _capHeight;
   117         CGFloat y2 = y1 + _side;
   118         CGFloat y3 = y2 + _capHeight;
   119         CGPoint p[6] = { {0,y1}, {x1,0}, {x2,y1}, {x2,y2}, {x1,y3}, {0,y2} };
   120         
   121         CGMutablePathRef path = CGPathCreateMutable();
   122         CGPathAddLines(path, NULL, p, 6);
   123         CGPathCloseSubpath(path);
   124         _cellPath = path;
   125     }
   126     return _cellPath;
   127 }
   128 
   129 
   130 @end
   131 
   132 
   133 
   134 
   135 
   136 #pragma mark -
   137 
   138 @implementation Hex
   139 
   140 
   141 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
   142 {
   143     CGContextSaveGState(ctx);
   144     CGPoint pos = self.position;
   145     CGContextTranslateCTM(ctx, pos.x, pos.y);
   146     CGContextBeginPath(ctx);
   147     CGContextAddPath(ctx, ((HexGrid*)_grid).cellPath);
   148     CGContextDrawPath(ctx, (fill ?kCGPathFill :kCGPathStroke));
   149     
   150     if( !fill && self.highlighted ) {
   151         // Highlight by drawing my outline in the highlight color:
   152         CGContextSetStrokeColorWithColor(ctx, self.borderColor);
   153         CGContextSetLineWidth(ctx,6);
   154         CGContextBeginPath(ctx);
   155         CGContextAddPath(ctx, ((HexGrid*)_grid).cellPath);
   156         CGContextDrawPath(ctx, kCGPathStroke);
   157     }
   158     CGContextRestoreGState(ctx);
   159 }
   160 
   161 
   162 - (BOOL)containsPoint:(CGPoint)p
   163 {
   164     return [super containsPoint: p]
   165         && CGPathContainsPoint( ((HexGrid*)_grid).cellPath, NULL, p, NO );
   166 }
   167 
   168 
   169 - (void) setHighlighted: (BOOL)highlighted
   170 {
   171     if( highlighted != self.highlighted ) {
   172         [super setHighlighted: highlighted];
   173         [_grid setNeedsDisplay];        // So I'll be asked to redraw myself
   174     }
   175 }
   176 
   177 
   178 #pragma mark -
   179 #pragma mark NEIGHBORS:
   180 
   181 
   182 - (NSArray*) neighbors
   183 {
   184     NSMutableArray *neighbors = [NSMutableArray arrayWithCapacity: 6];
   185     Hex* n[6] = {self.nw, self.ne, self.w, self.e, self.sw, self.se};
   186     for( int i=0; i<6; i++ )
   187         if( n[i] )
   188             [neighbors addObject: n[i]];
   189     return neighbors;
   190 }
   191 
   192 - (Hex*) nw     {return (Hex*)[_grid cellAtRow: _row+1 column: _column - ((_row+1)&1)];}
   193 - (Hex*) ne     {return (Hex*)[_grid cellAtRow: _row+1 column: _column + (_row&1)];}
   194 - (Hex*) e      {return (Hex*)[_grid cellAtRow: _row   column: _column + 1];}
   195 - (Hex*) se     {return (Hex*)[_grid cellAtRow: _row-1 column: _column + (_row&1)];}
   196 - (Hex*) sw     {return (Hex*)[_grid cellAtRow: _row-1 column: _column - ((_row-1)&1)];}
   197 - (Hex*) w      {return (Hex*)[_grid cellAtRow: _row   column: _column - 1];}
   198 
   199 // Directions relative to the current player:
   200 - (Hex*) fl     {return self.fwdIsN ?self.nw :self.se;}
   201 - (Hex*) fr     {return self.fwdIsN ?self.ne :self.sw;}
   202 - (Hex*) r      {return self.fwdIsN ?self.e  :self.w;}
   203 - (Hex*) br     {return self.fwdIsN ?self.se :self.nw;}
   204 - (Hex*) bl     {return self.fwdIsN ?self.sw :self.ne;}
   205 - (Hex*) l      {return self.fwdIsN ?self.w  :self.e;}
   206 
   207 
   208 @end