/* TODOs * -Introduce "gameover" logic * -Game tree search and heuristics for AI opponent */ #import "GameBoard.h" enum { PIECE_MARGIN = 4, HIGHLIGHT_MARGIN = 4 }; //handy macros that make reading the code a bit easier and save some typing //a way of designating an undefined point on the board #define NIL_POINT NSMakePoint(-1,-1) //ways for checking if two NSPoints are equal or not #define EQUAL_NSPOINTS(p1,p2) (p1.x == p2.x && p1.y == p2.y) #define NOTEQUAL_NSPOINTS(p1,p2) (p1.x != p2.x || p1.y != p2.y) //descriptive shortcuts for checking board conditions #define PLAYER_MOVING_NOT_AT(x,y) (board[(int)(x)][(int)(y)] != playerMoving) #define OPPONENT_NOT_BLOCKING(c1,p1,p2) (![self opponentInLineBetweenCoord:(c1) andCoord:NSMakePoint((p1),(p2))]) #define OPPONENT_AT(x,y) ( (board[(int)(x)][(int)(y)] != EMPTY && board[(int)(x)][(int)(y)] != playerMoving) ) //NSPoints (structs) can't be loaded into arrays, so wrap/retrieve them via NSValue #define POINT_OBJECT(x,y) ([NSValue valueWithPoint:NSMakePoint((x),(y))]) @implementation GameBoard //designated initializer for superclass - (id) initWithFrame:(NSRect) frame { if (self = [super initWithFrame: frame]) { //set up player colors playerColors[PLAYER_1] = [NSColor redColor]; playerColors[PLAYER_2] = [NSColor blackColor]; //set up board colors boardColors[0] = [NSColor lightGrayColor]; boardColors[1] = [NSColor darkGrayColor]; //initialize board int i; int j; for (i=0; i< DIMENSION; i++) for (j=0; j 0) { r.origin.x += 2; r.origin.y += 2; r.size.width -= 4; r.size.height -= 4; texture = [NSBezierPath bezierPathWithOvalInRect:r]; [[NSColor whiteColor] set]; [texture stroke]; } return path; } //based on a mouse click, determine the corresponding board coordinate -(NSPoint)boardCoordForClickPoint:(NSPoint)p { int x=-1; int y=-1; while (p.x > 0) { p.x -= squareDim; x++; } while (p.y > 0) { p.y -= squareDim; y++; } return NSMakePoint(x,y); } //used for moving - (void)movePieceFromCoord:(NSPoint)p1 toCoord:(NSPoint)p2 { //make sure move isn't in place if (p1.x == p2.x && p1.y == p2.y) return; //make sure there is a piece at p1 if (board[(int)p1.x][(int)p1.y] == EMPTY) return; //update the board int player = board[(int)p1.x][(int)p1.y]; board[(int)p1.x][(int)p1.y] = EMPTY; board[(int)p2.x][(int)p2.y] = player; [self setNeedsDisplay:YES]; } - (void)mouseDown:(NSEvent*)event { //get the mouse position in view coordinates NSPoint mouse; mouse = [self convertPoint: [event locationInWindow] fromView: nil]; //if there was a previous click, save it NSPoint previousPoint = selectedCoord; //get the new board square that was clicked in. selectedCoord = [self boardCoordForClickPoint:mouse]; //starting point for a move must be the player moving if ( EQUAL_NSPOINTS(previousPoint, NIL_POINT) && board[(int)selectedCoord.x][(int)selectedCoord.y] != playerMoving ) { selectedCoord = NIL_POINT; return; } //ending point for a move must not be on the player moving if (NOTEQUAL_NSPOINTS(previousPoint, NIL_POINT)) if (board[(int)selectedCoord.x][(int)selectedCoord.y] == playerMoving) hoveredCoord = selectedCoord; else if (![currentPlayerMoves containsObject:[NSValue valueWithPoint:selectedCoord]]) { //ignore it. it's not a valid move if (NOTEQUAL_NSPOINTS(previousPoint, NIL_POINT)) { selectedCoord = previousPoint; return; } } //generate moves here if (currentPlayerMoves) [currentPlayerMoves release]; currentPlayerMoves = [[self validMovesFromSpot:selectedCoord] retain]; if ( NOTEQUAL_NSPOINTS(previousPoint, NIL_POINT) && board[(int)selectedCoord.x][(int)selectedCoord.y] != playerMoving ) { [self movePieceFromCoord:previousPoint toCoord:selectedCoord]; selectedCoord = NIL_POINT; [currentPlayerMoves release]; currentPlayerMoves = nil; if (playerMoving == PLAYER_1) playerMoving = PLAYER_2; else playerMoving = PLAYER_1; } //set redisplay now that currentPlayerMoves is populated [self setNeedsDisplay:YES]; } - (void)mouseMoved:(NSEvent *)event { // get the mouse position in view coordinates NSPoint mouse; mouse = [self convertPoint: [event locationInWindow] fromView: nil]; //no need to redraw anything in this case, the piece is still highlighted if (NOTEQUAL_NSPOINTS(hoveredCoord, NIL_POINT) && [piecesPaths[(int)hoveredCoord.x][(int)hoveredCoord.y] containsPoint: mouse]) return; //otherwise, check to see if a piece is being moused over int i; int j; for (i = 0; i < DIMENSION; i++) for (j = 0; j < DIMENSION; j++) if (board[i][j] != EMPTY) if ([piecesPaths[i][j] containsPoint: mouse] && EQUAL_NSPOINTS(selectedCoord, NIL_POINT)) { hoveredCoord = NSMakePoint(i,j); [self setNeedsDisplay:YES]; return; } //no pieces are being moused over if we've made it to here, so there's //no need to redraw unless we neeed to un-highlight a piece, but only //unhighlight if the piece is not unselected if (NOTEQUAL_NSPOINTS(hoveredCoord, NIL_POINT) && EQUAL_NSPOINTS(selectedCoord, NIL_POINT)) { [self setNeedsDisplay:YES]; hoveredCoord = NIL_POINT; } } //draws the view, although never called explicitly - (void)drawRect:(NSRect)rect { [self drawBoardBackgroundInRect:rect]; //draw all pieces on the board by using the "board" //also store the bezier paths for detecting mouseovers int i; int j; for (i=0; i < DIMENSION; i++) for (j=0; j < DIMENSION; j++) if (board[i][j] == PLAYER_1) { if (piecesPaths[i][j] != nil) [piecesPaths[i][j] release]; piecesPaths[i][j] = [[self drawPieceForCoord:NSMakePoint(i,j) andPlayer:PLAYER_1] retain]; } else if (board[i][j] == PLAYER_2) { if (piecesPaths[i][j] != nil) [piecesPaths[i][j] release]; piecesPaths[i][j] = [[self drawPieceForCoord:NSMakePoint(i,j) andPlayer:PLAYER_2] retain]; } } /////////////////////////////////////////////////////////////////////////////// // Brand new methods since Part 1 follow /////////////////////////////////////////////////////////////////////////////// //The param "andCoord" is not included in calculations. Coords up to, but not //including andCoord are checked for the presence of the opponent. This method //is component of move generation. -(BOOL)opponentInLineBetweenCoord:(NSPoint)c1 andCoord:(NSPoint)c2 { if (EQUAL_NSPOINTS(c1,c2)) return NO; //vertically if (c1.x == c2.x) { if ((int)(c2.y-c1.y) > 0) { //up int dy; for (dy=c1.y; dy < c2.y; dy++) if (OPPONENT_AT(c1.x,dy)) return YES; } else { int dy; for (dy=c1.y; dy > c2.y; dy--) if (OPPONENT_AT(c1.x,dy)) return YES; } } //horizontally else if (c1.y == c2.y) { if ((int)(c2.x-c1.x) > 0) { int dx; for (dx=c1.x; dx < c2.x; dx++) if (OPPONENT_AT(dx,c1.y)) return YES; } else { int dx; for (dx=c1.x; dx > c2.x; dx--) if (OPPONENT_AT(dx,c1.y)) return YES; } } //diagonals else if ((int)(c2.y-c1.y) > 0) { //up if ((int)(c2.x-c1.x) > 0) { //up right int dx; int dy; for (dx=c1.x, dy=c1.y; dx < c2.x && dy < c2.y; dx++, dy++) if (OPPONENT_AT(dx,dy)) return YES; } else { //up left int dx; int dy; for (dx=c1.x, dy=c1.y; dx > c2.x && dy < c2.y; dx--, dy++) if (OPPONENT_AT(dx,dy)) return YES; } } else if ((int)(c2.y-c1.y) < 0) { //down if ((int)(c2.x-c1.x) > 0) { //down right int dx; int dy; for (dx=c1.x, dy=c1.y; dx < c2.x && dy > c2.y; dx++, dy--) if (OPPONENT_AT(dx,dy)) return YES; } else { //down left int dx; int dy; for (dx=c1.x, dy=c1.y; dx > c2.x && dy > c2.y; dx--, dy--) if (OPPONENT_AT(dx,dy)) return YES; } } else { NSLog(@"invalid input to opponentInLineBetweenCoord:andCoord"); return YES; } return NO; } //Counting the number of pieces in each "line of action" is essential //to generating moves. Do it here and return results in a helper struct - (LineCounts)lineCountsForSpot:(NSPoint)spot { //up down int up_down = 0; int y; for (y=0; y < DIMENSION; y++) if (board[(int)spot.x][y] != EMPTY) up_down++; //left right int left_right = 0; int x; for (x=0; x < DIMENSION; x++) if (board[x][(int)spot.y] != EMPTY) left_right++; //up right, then down left int up_right_down_left = 0; x=spot.x; y=spot.y; while (x < DIMENSION && y < DIMENSION) { if (board[x][y] != EMPTY) up_right_down_left++; x++; y++; } x=spot.x; y=spot.y; while (x >= 0 && y >= 0) { if (board[x][y] != EMPTY) up_right_down_left++; x--; y--; } //remove duplicate count if (board[(int)spot.x][(int)spot.y] != EMPTY) up_right_down_left--; //up left, then down right int up_left_down_right = 0; x=spot.x; y=spot.y; while (x >= 0 && y < DIMENSION) { if (board[x][y] != EMPTY) up_left_down_right++; x--; y++; } x=spot.x; y=spot.y; while (x < DIMENSION && y >= 0) { if (board[x][y] != EMPTY) up_left_down_right++; x++; y--; } //remove duplicate count if (board[(int)spot.x][(int)spot.y] != EMPTY) up_left_down_right--; //NSLog(@"up_down=%d, left_right=%d, up_right_down_left=%d, up_left_down_right=%d",up_down,left_right, up_right_down_left,up_left_down_right); LineCounts lc = { .up_down = up_down, .left_right = left_right, .up_left_down_right = up_left_down_right, .up_right_down_left = up_right_down_left }; return lc; } //Return all of the valid moves for the current given board from //position "spot" -(NSArray*)validMovesFromSpot:(NSPoint)spot { //make sure spot is occupied or else there's no move if (board[(int)spot.x][(int)spot.y] == EMPTY) return [NSArray array]; LineCounts lc; lc = [self lineCountsForSpot:spot]; //NSLog(@"ud=%d, lr=%d, uldr=%d, urdl=%d",foo.up_down, foo.left_right, foo.up_left_down_right, foo.up_right_down_left); NSMutableArray *moves = [NSMutableArray arrayWithCapacity:8]; //up if ( spot.y + lc.up_down < DIMENSION && PLAYER_MOVING_NOT_AT(spot.x,spot.y+lc.up_down) && OPPONENT_NOT_BLOCKING(spot,spot.x,spot.y+lc.up_down) ) [moves addObject:POINT_OBJECT(spot.x,spot.y+lc.up_down)]; //down if ( spot.y - lc.up_down >= 0 && PLAYER_MOVING_NOT_AT(spot.x, spot.y-lc.up_down) && OPPONENT_NOT_BLOCKING(spot,spot.x, spot.y-lc.up_down) ) [moves addObject:POINT_OBJECT(spot.x, spot.y-lc.up_down)]; //left if ( spot.x - lc.left_right >= 0 && PLAYER_MOVING_NOT_AT(spot.x-lc.left_right, spot.y) && OPPONENT_NOT_BLOCKING(spot,spot.x-lc.left_right, spot.y) ) [moves addObject:POINT_OBJECT(spot.x-lc.left_right, spot.y)]; //right if ( spot.x + lc.left_right < DIMENSION && PLAYER_MOVING_NOT_AT(spot.x+lc.left_right,spot.y) && OPPONENT_NOT_BLOCKING(spot,spot.x+lc.left_right, spot.y) ) [moves addObject:POINT_OBJECT(spot.x+lc.left_right, spot.y)]; //up right if ( spot.x + lc.up_right_down_left < DIMENSION && spot.y + lc.up_right_down_left < DIMENSION && PLAYER_MOVING_NOT_AT(spot.x + lc.up_right_down_left,spot.y + lc.up_right_down_left) && OPPONENT_NOT_BLOCKING(spot,spot.x+lc.up_right_down_left, spot.y+lc.up_right_down_left) ) [moves addObject:POINT_OBJECT(spot.x+lc.up_right_down_left, spot.y+lc.up_right_down_left)]; //down left if ( spot.x - lc.up_right_down_left >= 0 && spot.y - lc.up_right_down_left >= 0 && PLAYER_MOVING_NOT_AT(spot.x - lc.up_right_down_left,spot.y - lc.up_right_down_left) && OPPONENT_NOT_BLOCKING(spot,spot.x-lc.up_right_down_left, spot.y-lc.up_right_down_left) ) [moves addObject:POINT_OBJECT(spot.x-lc.up_right_down_left, spot.y-lc.up_right_down_left)]; //up left if ( spot.x - lc.up_left_down_right >= 0 && spot.y + lc.up_left_down_right < DIMENSION && PLAYER_MOVING_NOT_AT(spot.x - lc.up_left_down_right,spot.y + lc.up_left_down_right) && OPPONENT_NOT_BLOCKING(spot,spot.x-lc.up_left_down_right, spot.y+lc.up_left_down_right) ) [moves addObject:POINT_OBJECT(spot.x-lc.up_left_down_right, spot.y+lc.up_left_down_right)]; //down right if ( spot.x + lc.up_left_down_right < DIMENSION && spot.y - lc.up_left_down_right >= 0 && PLAYER_MOVING_NOT_AT(spot.x + lc.up_left_down_right, spot.y - lc.up_left_down_right) && OPPONENT_NOT_BLOCKING(spot,spot.x+lc.up_left_down_right, spot.y-lc.up_left_down_right) ) [moves addObject:POINT_OBJECT(spot.x+lc.up_left_down_right, spot.y-lc.up_left_down_right)]; return (NSArray*)moves; } //Used for hypothetical board evaluation by the AppController. This method reuses //existing code by temporarily replacing instance variables "board" and "playerMoving" //when invoked. memcopy is *fast*, so there's virtually no overhead in doing so. - (NSArray*)validMovesFromSpot:(NSPoint)spot withBoard:(int[][])b { //make sure spot is occupied or else there's no move if (board[(int)spot.x][(int)spot.y] == EMPTY) return [NSArray array]; //save the instance variable playerMoving //set up hypothetical player moving to be whoever is at 'spot' int saved_playerMoving = playerMoving; playerMoving = board[(int)spot.x][(int)spot.y]; //save the instance variable "board" int saved_board[DIMENSION][DIMENSION]; memcpy(saved_board, board, sizeof(board)); //make instance variable be whatever was passed in through b memcpy(board, b, sizeof(board)); //calculate the valid moves as usual now that the hypothetical //board "b" is in place NSArray *moves = [self validMovesFromSpot:spot]; //restore the instance variable "board" and "playerMoving" memcpy(board, saved_board, sizeof(board)); playerMoving = saved_playerMoving; return moves; } //Uses a simple wrapper to facilitate passing back a 2d array - (BoardStruct) board { BoardStruct bs; memcpy(bs.board, board, sizeof(board)); return bs; } @end