The second version of Peg Solitaire adds drawing of the pegboard
and pegs. I haven't figured out the storage of the Pegs themselves
(there's not a whole lot of state to make a Peg class worthwhile
yet), so right now the 15 pegs and their locations are stored as
arrays in the CHPegBoard object:
CHPegBoard.h
#import <Cocoa/Cocoa.h>
#define PEG_COUNT 15
@interface CHPegBoard : NSView
{
BOOL pegStates[PEG_COUNT];
NSRect pegRects[PEG_COUNT];
}
@end // CHPegBoard
pegStates array, YES means the location has a peg,
NO means it is empty. pegRects holds the rectangle for each
peg.
CHPegBoard.m initialize the object (setting
the peg on and off values set) and some utility code for centering a
rectangle inside another once (which will come in handy later when we
lay out the pegs)
CHPegBoard.m
#import "CHPegBoard.h"
@implementation CHPegBoard
// designated initializer for our superclass.
// set up the pegStates (on/off) array here, since this is still
// 'scaffolding to get stuff working' mode
- (id) initWithFrame: (NSRect) frame
{
if (self = [super initWithFrame: frame]) {
int i;
// alternate them on and off, for initial development
for (i = 0; i < PEG_COUNT; i++) {
pegStates[i] = ((i % 2) == 0) ? YES : NO;
}
}
return (self);
} // initWIthFrame
// utility method that ideally should be in some common library
// or provided by Apple.
// Given two rectangles, center one inside of the other
- (NSRect) centerRect: (NSRect) smallRect
inRect: (NSRect) bigRect
{
NSRect centerRect;
centerRect.size = smallRect.size;
centerRect.origin.x = (bigRect.size.width - smallRect.size.width) / 2.0;
centerRect.origin.y = (bigRect.size.height - smallRect.size.height) / 2.0;
return (centerRect);
} // centerRect
// create a maximal-sized square that fits inside of our view.
- (NSRect) makeCenterSquareRect
{
// our bounding rectangle
NSRect bounds = [self bounds];
// figure out whether the width or height is smaller. That's our
// controlling dimension
float minDimension;
minDimension = MIN (bounds.size.width, bounds.size.height);
// make a square the proper size. This has an origin of zero, but
// can be based anywhere since [self centerRect] will change the
// x and y
NSRect centerSquare = NSMakeRect (0.0, 0.0, minDimension, minDimension);
// and now center it in our bounds
centerSquare = [self centerRect: centerSquare
inRect: bounds];
// and skootch it in to make it not coincident with the view border.
// makes it look a little nicer
centerSquare = NSInsetRect (centerSquare, 5, 5);
return (centerSquare);
} // makeCenterSquareRect
Setting up horizontal rows of pegs was actually pretty easy. Calculating the vertical locations is easy, and distributing pegs evenly across in a row is really easy. Getting the nice offset-stairstep effect of the peg solitaire board is harder to get to look good.
So, the pegs have their rectangles calculated like a right-triangle:
Then the union of each row was calculated:
And then these rows are centered.
CHPegBoard.m
- (void) recalcPegRectsForRect: (NSRect) squareRect
{
// first make all the rects flush-left
// these magic constants were determined via tweaking and fiddling
float pegRadius = squareRect.size.width / 8;
float pad = (squareRect.size.width - (pegRadius * 5)) / 7.0;
float rowHeight = squareRect.size.height / 6.0;
float x, y;
// union rectangle for each row
NSRect unions[5];
int i = 0; // which peg we're looking at
int row;
for (row = 0; row < 5; row++) {
y = pad + rowHeight * row;
x = 0;
// do the pegs of the row.
int peg;
for (peg = 0; peg < 5 - row; peg++) {
pegRects[i] = NSMakeRect (x, y, pegRadius, pegRadius);
x += pegRadius + pad;
if (peg == 0) {
unions[row] = pegRects[i];
} else {
unions[row] = NSUnionRect (unions[row], pegRects[i]);
}
i++;
}
}
// now center each rect in our useful area
NSRect centerSquare = [self makeCenterSquareRect];
i = 0;
for (row = 0; row < 5; row++) {
NSRect rect = [self centerRect: unions[row] inRect: centerSquare];
// now update the x values for the row, and update the y
// for the start of the center rect
int peg;
for (peg = 0; peg < 5 - row; peg++) {
pegRects[i].origin.x += rect.origin.x + centerSquare.origin.x;
pegRects[i].origin.y += centerSquare.origin.y;
i++;
}
}
} // recalcPegRectsForRect
// given a peg index, draw it.
- (void) drawPeg: (int) i
{
assert (i >= 0 && i < PEG_COUNT);
// right now, just make 'on' pegs black, and 'off' pegs white
// eventually we can do much snazzier pegs
NSRect pegRect = pegRects[i];
if (pegStates[i]) {
[[NSColor blackColor] set];
} else {
[[NSColor whiteColor] set];
}
NSBezierPath *path;
path = [NSBezierPath bezierPathWithOvalInRect: pegRect];
[path fill];
[[NSColor blackColor] set];
[path stroke];
} // drawPeg
- (void) drawBoardInRect: (NSRect) rect
{
// draw the base board, using a path of a triangle
// since not an alloc or a copy, this can be considered to be
// autoreleased and we don't have to worry about it being cleaned up
NSBezierPath *path = [NSBezierPath bezierPath];
// move to the bottom-left
[path moveToPoint: rect.origin];
// line to the top-middle
NSPoint point;
point.x = rect.origin.x + rect.size.width / 2.0;
point.y = rect.origin.y + rect.size.height;
[path lineToPoint: point];
// line to the bottom-right
point.x = rect.origin.x + rect.size.width;
point.y = rect.origin.y;
[path lineToPoint: point];
// and close the path by adding a line back to the bottom left
[path closePath];
[[NSColor brownColor] set];
[path fill];
[[NSColor blackColor] set];
[path stroke];
} // drawBoardInRect
drawRect: method. It keeps the
background drawing from the previous version. Then it calculates
the center-most rectangle and stroked so we can see what it does.
(it'll get taken out eventually). The board is then drawn and the
pegs drawn on top of it.
- (void) drawRect: (NSRect) rect
{
// get the bounds, since the rect might be a smaller area to redraw
NSRect bounds = [self bounds];
// make a nice white background to draw on
[[NSColor whiteColor] set];
[NSBezierPath fillRect: bounds];
// this area is where the triangular pegboard will go
NSRect squareRect = [self makeCenterSquareRect];
// pretty heavy-handed doing this for each re-draw.
// should just look for resizes to happen
[self recalcPegRectsForRect: squareRect];
// draw it in blue so we can see the rectangle we're using.
// this will go away in the final version
[[NSColor blueColor] set];
[NSBezierPath strokeRect: squareRect];
// draw the board
[self drawBoardInRect: squareRect];
// draw the pegs on top of the board
int i;
for (i = 0; i < PEG_COUNT; i++) {
[self drawPeg: i];
}
[[NSColor blackColor] set];
[NSBezierPath strokeRect: bounds];
} // drawRect