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.
27 @implementation HexGrid
30 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
31 spacing: (CGSize)spacing
32 position: (CGPoint)pos
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;
39 self = [super initWithRows: nRows columns: nColumns
43 _capHeight = capHeight;
45 self.bounds = CGRectMake(-1, -1,
46 (nColumns+0.5)*spacing.width + 2,
47 nRows*spacing.height+capHeight + 2);
48 _cellClass = [Hex class];
54 - (id) initWithRows: (unsigned)nRows columns: (unsigned)nColumns
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 return [self initWithRows: nRows columns: nColumns
61 spacing: CGSizeMake(s,s)
62 position: frame.origin];
68 CGPathRelease(_cellPath);
73 - (void) addCellsInHexagon
75 int size = _nRows - !(_nRows & 1); // make it odd
76 for( int row=0; row<_nRows; row++ ) {
77 int n; // # of hexes remaining in this row
79 n = size - abs(row-size/2);
82 int c0 = floor(((int)_nRows+1-n -(row&1))/2.0); // col of 1st remaining hex
84 for( int col=0; col<_nColumns; col++ )
85 if( col>=c0 && col<c0+n )
86 [self addCellAtRow: row column: col];
91 - (GridCell*) createCellAtRow: (unsigned)row column: (unsigned)col
92 suggestedFrame: (CGRect)frame
94 // Overridden to stagger the odd-numbered rows
96 frame.origin.x += _spacing.width/2;
97 frame.size.height += _capHeight;
98 return [super createCellAtRow: row column: col suggestedFrame: frame];
102 // Returns a hexagonal CG path defining a cell's outline. Used by cells when drawing & hit-testing.
103 - (CGPathRef) cellPath
106 CGFloat x1 = _spacing.width/2;
107 CGFloat x2 = _spacing.width;
108 CGFloat y1 = _capHeight;
109 CGFloat y2 = y1 + _side;
110 CGFloat y3 = y2 + _capHeight;
111 CGPoint p[6] = { {0,y1}, {x1,0}, {x2,y1}, {x2,y2}, {x1,y3}, {0,y2} };
113 CGMutablePathRef path = CGPathCreateMutable();
114 CGPathAddLines(path, NULL, p, 6);
115 CGPathCloseSubpath(path);
133 - (void) drawInParentContext: (CGContextRef)ctx fill: (BOOL)fill
135 CGContextSaveGState(ctx);
136 CGPoint pos = self.position;
137 CGContextTranslateCTM(ctx, pos.x, pos.y);
138 CGContextBeginPath(ctx);
139 CGContextAddPath(ctx, ((HexGrid*)_grid).cellPath);
140 CGContextDrawPath(ctx, (fill ?kCGPathFill :kCGPathStroke));
142 if( !fill && self.highlighted ) {
143 // Highlight by drawing my outline in the highlight color:
144 CGContextSetStrokeColorWithColor(ctx, self.borderColor);
145 CGContextSetLineWidth(ctx,6);
146 CGContextBeginPath(ctx);
147 CGContextAddPath(ctx, ((HexGrid*)_grid).cellPath);
148 CGContextDrawPath(ctx, kCGPathStroke);
150 CGContextRestoreGState(ctx);
154 - (BOOL)containsPoint:(CGPoint)p
156 return [super containsPoint: p]
157 && CGPathContainsPoint( ((HexGrid*)_grid).cellPath, NULL, p, NO );
161 - (void) setHighlighted: (BOOL)highlighted
163 if( highlighted != self.highlighted ) {
164 [super setHighlighted: highlighted];
165 [_grid setNeedsDisplay]; // So I'll be asked to redraw myself
171 #pragma mark NEIGHBORS:
174 - (NSArray*) neighbors
176 NSMutableArray *neighbors = [NSMutableArray arrayWithCapacity: 6];
177 Hex* n[6] = {self.nw, self.ne, self.w, self.e, self.sw, self.se};
178 for( int i=0; i<6; i++ )
180 [neighbors addObject: n[i]];
184 - (Hex*) nw {return (Hex*)[_grid cellAtRow: _row+1 column: _column - ((_row+1)&1)];}
185 - (Hex*) ne {return (Hex*)[_grid cellAtRow: _row+1 column: _column + (_row&1)];}
186 - (Hex*) e {return (Hex*)[_grid cellAtRow: _row column: _column + 1];}
187 - (Hex*) se {return (Hex*)[_grid cellAtRow: _row-1 column: _column + (_row&1)];}
188 - (Hex*) sw {return (Hex*)[_grid cellAtRow: _row-1 column: _column - ((_row-1)&1)];}
189 - (Hex*) w {return (Hex*)[_grid cellAtRow: _row column: _column - 1];}
191 // Directions relative to the current player:
192 - (Hex*) fl {return self.fwdIsN ?self.nw :self.se;}
193 - (Hex*) fr {return self.fwdIsN ?self.ne :self.sw;}
194 - (Hex*) r {return self.fwdIsN ?self.e :self.w;}
195 - (Hex*) br {return self.fwdIsN ?self.se :self.nw;}
196 - (Hex*) bl {return self.fwdIsN ?self.sw :self.ne;}
197 - (Hex*) l {return self.fwdIsN ?self.w :self.e;}