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