/* * board.c -- platform-independent drawing code for XBoard * * Copyright 1991 by Digital Equipment Corporation, Maynard, * Massachusetts. * * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006, * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free * Software Foundation, Inc. * * The following terms apply to Digital Equipment Corporation's copyright * interest in XBoard: * ------------------------------------------------------------------------ * All Rights Reserved * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose and without fee is hereby granted, * provided that the above copyright notice appear in all copies and that * both that copyright notice and this permission notice appear in * supporting documentation, and that the name of Digital not be * used in advertising or publicity pertaining to distribution of the * software without specific, written prior permission. * * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS * SOFTWARE. * ------------------------------------------------------------------------ * * The following terms apply to the enhanced version of XBoard * distributed by the Free Software Foundation: * ------------------------------------------------------------------------ * * GNU XBoard is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * GNU XBoard is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * *------------------------------------------------------------------------ ** See the file ChangeLog for a revision history. */ #define HIGHDRAG 1 #include "config.h" #include #include #include #include #include #include #include #include #if STDC_HEADERS # include # include #else /* not STDC_HEADERS */ extern char *getenv(); # if HAVE_STRING_H # include # else /* not HAVE_STRING_H */ # include # endif /* not HAVE_STRING_H */ #endif /* not STDC_HEADERS */ #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #if HAVE_UNISTD_H # include #endif #if HAVE_SYS_WAIT_H # include #endif #include "common.h" #include "frontend.h" #include "backend.h" #include "xboard2.h" #include "moves.h" #include "board.h" #include "draw.h" #ifdef __EMX__ #ifndef HAVE_USLEEP #define HAVE_USLEEP #endif #define usleep(t) _sleep2(((t)+500)/1000) #endif int squareSize, lineGap; int damage[2][BOARD_RANKS][BOARD_FILES]; /* There can be two pieces being animated at once: a player can begin dragging a piece before the remote opponent has moved. */ AnimState anims[NrOfAnims]; static void DrawSquare P((int row, int column, ChessSquare piece, int do_flash)); static Boolean IsDrawArrowEnabled P((void)); static void DrawArrowHighlight P((int fromX, int fromY, int toX,int toY)); static void ArrowDamage P((int s_col, int s_row, int d_col, int d_row)); static void drawHighlight (int file, int rank, int type) { int x, y; if (lineGap == 0) return; if (flipView) { x = lineGap/2 + ((BOARD_WIDTH-1)-file) * (squareSize + lineGap); y = lineGap/2 + rank * (squareSize + lineGap); } else { x = lineGap/2 + file * (squareSize + lineGap); y = lineGap/2 + ((BOARD_HEIGHT-1)-rank) * (squareSize + lineGap); } DrawBorder(x,y, type, lineGap & 1); // pass whether lineGap is odd } int hi1X = -1, hi1Y = -1, hi2X = -1, hi2Y = -1; int pm1X = -1, pm1Y = -1, pm2X = -1, pm2Y = -1; void SetHighlights (int fromX, int fromY, int toX, int toY) { // [HGM] schedule old for erasure, and leave drawing new to DrawPosition int change = 0; if (hi1X >= 0 && hi1Y >= 0) { if (hi1X != fromX || hi1Y != fromY) { damage[0][hi1Y][hi1X] |= 2; change |= 1; } change |= 4; } if (hi2X >= 0 && hi2Y >= 0) { if (hi2X != toX || hi2Y != toY) { damage[0][hi2Y][hi2X] |= 2; change |= 2; } change |= 8; } if(change > 12 && IsDrawArrowEnabled()) ArrowDamage(hi1X, hi1Y, hi2X, hi2Y); hi1X = fromX; hi1Y = fromY; hi2X = toX; hi2Y = toY; } void ClearHighlights () { SetHighlights(-1, -1, -1, -1); } void SetPremoveHighlights (int fromX, int fromY, int toX, int toY) { if (pm1X != fromX || pm1Y != fromY) { if (pm1X >= 0 && pm1Y >= 0) { damage[0][pm1Y][pm1X] |= 2; } } if (pm2X != toX || pm2Y != toY) { if (pm2X >= 0 && pm2Y >= 0) { damage[0][pm2Y][pm2X] |= 2; } } pm1X = fromX; pm1Y = fromY; pm2X = toX; pm2Y = toY; } void ClearPremoveHighlights () { SetPremoveHighlights(-1, -1, -1, -1); } /* * If the user selects on a border boundary, return -1; if off the board, * return -2. Otherwise map the event coordinate to the square. */ int EventToSquare (int x, int limit) { if (x <= 0) return -2; if (x < lineGap) return -1; x -= lineGap; if ((x % (squareSize + lineGap)) >= squareSize) return -1; x /= (squareSize + lineGap); if (x >= limit) return -2; return x; } /* [HR] determine square color depending on chess variant. */ int SquareColor (int row, int column) { int square_color; if (gameInfo.variant == VariantXiangqi) { if (column >= 3 && column <= 5 && row >= 0 && row <= 2) { square_color = 1; } else if (column >= 3 && column <= 5 && row >= 7 && row <= 9) { square_color = 0; } else if (row <= 4) { square_color = 0; } else { square_color = 1; } } else { square_color = ((column + row) % 2) == 1; } /* [hgm] holdings: next line makes all holdings squares light */ if(column < BOARD_LEFT || column >= BOARD_RGHT) square_color = 1; if ( // [HGM] holdings: blank out area between board and holdings column == BOARD_LEFT-1 || column == BOARD_RGHT || (column == BOARD_LEFT-2 && row < BOARD_HEIGHT-gameInfo.holdingsSize) || (column == BOARD_RGHT+1 && row >= gameInfo.holdingsSize) ) square_color = 2; // black return square_color; } /* Convert board position to corner of screen rect and color */ void ScreenSquare (int column, int row, Pnt *pt, int *color) { if (flipView) { pt->x = lineGap + ((BOARD_WIDTH-1)-column) * (squareSize + lineGap); pt->y = lineGap + row * (squareSize + lineGap); } else { pt->x = lineGap + column * (squareSize + lineGap); pt->y = lineGap + ((BOARD_HEIGHT-1)-row) * (squareSize + lineGap); } *color = SquareColor(row, column); } /* Convert window coords to square */ void BoardSquare (int x, int y, int *column, int *row) { *column = EventToSquare(x, BOARD_WIDTH); if (flipView && *column >= 0) *column = BOARD_WIDTH - 1 - *column; *row = EventToSquare(y, BOARD_HEIGHT); if (!flipView && *row >= 0) *row = BOARD_HEIGHT - 1 - *row; } /* Generate a series of frame coords from start->mid->finish. The movement rate doubles until the half way point is reached, then halves back down to the final destination, which gives a nice slow in/out effect. The algorithmn may seem to generate too many intermediates for short moves, but remember that the purpose is to attract the viewers attention to the piece about to be moved and then to where it ends up. Too few frames would be less noticeable. */ static void Tween (Pnt *start, Pnt *mid, Pnt *finish, int factor, Pnt frames[], int *nFrames) { int fraction, n, count; count = 0; /* Slow in, stepping 1/16th, then 1/8th, ... */ fraction = 1; for (n = 0; n < factor; n++) fraction *= 2; for (n = 0; n < factor; n++) { frames[count].x = start->x + (mid->x - start->x) / fraction; frames[count].y = start->y + (mid->y - start->y) / fraction; count ++; fraction = fraction / 2; } /* Midpoint */ frames[count] = *mid; count ++; /* Slow out, stepping 1/2, then 1/4, ... */ fraction = 2; for (n = 0; n < factor; n++) { frames[count].x = finish->x - (finish->x - mid->x) / fraction; frames[count].y = finish->y - (finish->y - mid->y) / fraction; count ++; fraction = fraction * 2; } *nFrames = count; } /**** Animation code by Hugh Fisher, DCS, ANU. Known problem: if a window overlapping the board is moved away while a piece is being animated underneath, the newly exposed area won't be updated properly. I can live with this. Known problem: if you look carefully at the animation of pieces in mono mode, they are being drawn as solid shapes without interior detail while moving. Fixing this would be a major complication for minimal return. ****/ /* Utilities */ #undef Max /* just in case */ #undef Min #define Max(a, b) ((a) > (b) ? (a) : (b)) #define Min(a, b) ((a) < (b) ? (a) : (b)) typedef struct { short int x, y, width, height; } MyRectangle; void DoSleep (int n) { FrameDelay(n); } static void SetRect (MyRectangle *rect, int x, int y, int width, int height) { rect->x = x; rect->y = y; rect->width = width; rect->height = height; } /* Test if two frames overlap. If they do, return intersection rect within old and location of that rect within new. */ static Boolean Intersect ( Pnt *old, Pnt *new, int size, MyRectangle *area, Pnt *pt) { if (old->x > new->x + size || new->x > old->x + size || old->y > new->y + size || new->y > old->y + size) { return False; } else { SetRect(area, Max(new->x - old->x, 0), Max(new->y - old->y, 0), size - abs(old->x - new->x), size - abs(old->y - new->y)); pt->x = Max(old->x - new->x, 0); pt->y = Max(old->y - new->y, 0); return True; } } /* For two overlapping frames, return the rect(s) in the old that do not intersect with the new. */ static void CalcUpdateRects (Pnt *old, Pnt *new, int size, MyRectangle update[], int *nUpdates) { int count; /* If old = new (shouldn't happen) then nothing to draw */ if (old->x == new->x && old->y == new->y) { *nUpdates = 0; return; } /* Work out what bits overlap. Since we know the rects are the same size we don't need a full intersect calc. */ count = 0; /* Top or bottom edge? */ if (new->y > old->y) { SetRect(&(update[count]), old->x, old->y, size, new->y - old->y); count ++; } else if (old->y > new->y) { SetRect(&(update[count]), old->x, old->y + size - (old->y - new->y), size, old->y - new->y); count ++; } /* Left or right edge - don't overlap any update calculated above. */ if (new->x > old->x) { SetRect(&(update[count]), old->x, Max(new->y, old->y), new->x - old->x, size - abs(new->y - old->y)); count ++; } else if (old->x > new->x) { SetRect(&(update[count]), new->x + size, Max(new->y, old->y), old->x - new->x, size - abs(new->y - old->y)); count ++; } /* Done */ *nUpdates = count; } /* Animate the movement of a single piece */ static void BeginAnimation (AnimNr anr, ChessSquare piece, ChessSquare bgPiece, int startColor, Pnt *start) { AnimState *anim = &anims[anr]; if(appData.upsideDown && flipView) piece += piece < BlackPawn ? BlackPawn : -BlackPawn; /* The old buffer is initialised with the start square (empty) */ if(bgPiece == EmptySquare) { DrawBlank(anr, start->x, start->y, startColor); } else { /* Kludge alert: When gating we want the introduced piece to appear on the from square. To generate an image of it, we draw it on the board, copy the image, and draw the original piece again. */ if(piece != bgPiece) DrawSquare(anim->startBoardY, anim->startBoardX, bgPiece, 0); CopyRectangle(anr, DISP, 2, start->x, start->y, squareSize, squareSize, 0, 0); // [HGM] zh: unstack in stead of grab if(piece != bgPiece) DrawSquare(anim->startBoardY, anim->startBoardX, piece, 0); } anim->prevFrame = *start; SetDragPiece(anr, piece); } static void AnimationFrame (AnimNr anr, Pnt *frame, ChessSquare piece) { MyRectangle updates[4]; MyRectangle overlap; Pnt pt; AnimState *anim = &anims[anr]; int count, i, x, y, w, h; /* Save what we are about to draw into the new buffer */ CopyRectangle(anr, DISP, 0, x = frame->x, y = frame->y, w = squareSize, h = squareSize, 0, 0); /* Erase bits of the previous frame */ if (Intersect(&anim->prevFrame, frame, squareSize, &overlap, &pt)) { /* Where the new frame overlapped the previous, the contents in newBuf are wrong. */ CopyRectangle(anr, 2, 0, overlap.x, overlap.y, overlap.width, overlap.height, pt.x, pt.y); /* Repaint the areas in the old that don't overlap new */ CalcUpdateRects(&anim->prevFrame, frame, squareSize, updates, &count); for (i = 0; i < count; i++) CopyRectangle(anr, 2, DISP, updates[i].x - anim->prevFrame.x, updates[i].y - anim->prevFrame.y, updates[i].width, updates[i].height, updates[i].x, updates[i].y); /* [HGM] correct expose rectangle to encompass both overlapping squares */ if(x > anim->prevFrame.x) w += x - anim->prevFrame.x, x = anim->prevFrame.x; else w += anim->prevFrame.x - x; if(y > anim->prevFrame.y) h += y - anim->prevFrame.y, y = anim->prevFrame.y; else h += anim->prevFrame.y - y; } else { /* Easy when no overlap */ CopyRectangle(anr, 2, DISP, 0, 0, squareSize, squareSize, anim->prevFrame.x, anim->prevFrame.y); GraphExpose(currBoard, anim->prevFrame.x, anim->prevFrame.y, squareSize, squareSize); } /* Save this frame for next time round */ CopyRectangle(anr, 0, 2, 0, 0, squareSize, squareSize, 0, 0); anim->prevFrame = *frame; /* Draw piece over original screen contents, not current, and copy entire rect. Wipes out overlapping piece images. */ InsertPiece(anr, piece); CopyRectangle(anr, 0, DISP, 0, 0, squareSize, squareSize, frame->x, frame->y); GraphExpose(currBoard, x, y, w, h); } static void EndAnimation (AnimNr anr, Pnt *finish) { MyRectangle updates[4]; MyRectangle overlap; Pnt pt; int count, i; AnimState *anim = &anims[anr]; /* The main code will redraw the final square, so we only need to erase the bits that don't overlap. */ if (Intersect(&anim->prevFrame, finish, squareSize, &overlap, &pt)) { CalcUpdateRects(&anim->prevFrame, finish, squareSize, updates, &count); for (i = 0; i < count; i++) CopyRectangle(anr, 2, DISP, updates[i].x - anim->prevFrame.x, updates[i].y - anim->prevFrame.y, updates[i].width, updates[i].height, updates[i].x, updates[i].y); } else { CopyRectangle(anr, 2, DISP, 0, 0, squareSize, squareSize, anim->prevFrame.x, anim->prevFrame.y); } } static void FrameSequence (AnimNr anr, ChessSquare piece, int startColor, Pnt *start, Pnt *finish, Pnt frames[], int nFrames) { int n; BeginAnimation(anr, piece, EmptySquare, startColor, start); for (n = 0; n < nFrames; n++) { AnimationFrame(anr, &(frames[n]), piece); FrameDelay(appData.animSpeed); } EndAnimation(anr, finish); } void AnimateAtomicCapture (Board board, int fromX, int fromY, int toX, int toY) { int i, x, y; ChessSquare piece = board[fromY][toY]; board[fromY][toY] = EmptySquare; DrawPosition(FALSE, board); if (flipView) { x = lineGap + ((BOARD_WIDTH-1)-toX) * (squareSize + lineGap); y = lineGap + toY * (squareSize + lineGap); } else { x = lineGap + toX * (squareSize + lineGap); y = lineGap + ((BOARD_HEIGHT-1)-toY) * (squareSize + lineGap); } for(i=1; i<4*kFactor; i++) { int r = squareSize * 9 * i/(20*kFactor - 5); DrawDot(1, x + squareSize/2 - r, y+squareSize/2 - r, 2*r); FrameDelay(appData.animSpeed); } board[fromY][toY] = piece; DrawGrid(); } /* Main control logic for deciding what to animate and how */ void AnimateMove (Board board, int fromX, int fromY, int toX, int toY) { ChessSquare piece; int hop, x = toX, y = toY, x2 = kill2X; Pnt start, finish, mid; Pnt frames[kFactor * 2 + 1]; int nFrames, startColor, endColor; if(killX >= 0 && IS_LION(board[fromY][fromX])) Roar(); /* Are we animating? */ if (!appData.animate || appData.blindfold) return; if(board[toY][toX] == WhiteRook && board[fromY][fromX] == WhiteKing || board[toY][toX] == BlackRook && board[fromY][fromX] == BlackKing || board[toY][toX] == WhiteKing && board[fromY][fromX] == WhiteRook || // [HGM] seirawan board[toY][toX] == BlackKing && board[fromY][fromX] == BlackRook) return; // [HGM] FRC: no animtion of FRC castlings, as to-square is not true to-square if (fromY < 0 || fromX < 0 || toX < 0 || toY < 0) return; piece = board[fromY][fromX]; if (piece >= EmptySquare) return; if(x2 >= 0) toX = kill2X, toY = kill2Y; else if(killX >= 0) toX = killX, toY = killY; // [HGM] lion: first to kill square again: #if DONT_HOP hop = FALSE; #else hop = abs(fromX-toX) == 1 && abs(fromY-toY) == 2 || abs(fromX-toX) == 2 && abs(fromY-toY) == 1; #endif ScreenSquare(fromX, fromY, &start, &startColor); ScreenSquare(toX, toY, &finish, &endColor); if (hop) { /* Knight: make straight movement then diagonal */ if (abs(toY - fromY) < abs(toX - fromX)) { mid.x = start.x + (finish.x - start.x) / 2; mid.y = start.y; } else { mid.x = start.x; mid.y = start.y + (finish.y - start.y) / 2; } } else { mid.x = start.x + (finish.x - start.x) / 2; mid.y = start.y + (finish.y - start.y) / 2; } /* Don't use as many frames for very short moves */ if (abs(toY - fromY) + abs(toX - fromX) <= 2) Tween(&start, &mid, &finish, kFactor - 1, frames, &nFrames); else Tween(&start, &mid, &finish, kFactor, frames, &nFrames); FrameSequence(Game, piece, startColor, &start, &finish, frames, nFrames); if(Explode(board, fromX, fromY, toX, toY)) { // mark as damaged int i,j; for(i=0; i= 0 && anims[Player].dragPiece < EmptySquare) { ChessSquare bgPiece = EmptySquare; anims[Player].dragActive = True; if(boardX == BOARD_RGHT+1 && PieceForSquare(boardX-1, boardY) > 1 || boardX == BOARD_LEFT-2 && PieceForSquare(boardX+1, boardY) > 1) bgPiece = anims[Player].dragPiece; if(gatingPiece != EmptySquare) bgPiece = gatingPiece; BeginAnimation(Player, anims[Player].dragPiece, bgPiece, color, &corner); /* Mark this square as needing to be redrawn. Note that we don't remove the piece though, since logically (ie as seen by opponent) the move hasn't been made yet. */ damage[0][boardY][boardX] |= True; } else { anims[Player].dragActive = False; } } /* Handle expose event while piece being dragged */ static void DrawDragPiece () { if (!anims[Player].dragActive || appData.blindfold) return; /* What we're doing: logically, the move hasn't been made yet, so the piece is still in it's original square. But visually it's being dragged around the board. So we erase the square that the piece is on and draw it at the last known drag point. */ DrawOneSquare(anims[Player].startSquare.x, anims[Player].startSquare.y, EmptySquare, anims[Player].startColor, 0, NULL, NULL, 0); AnimationFrame(Player, &anims[Player].prevFrame, anims[Player].dragPiece); damage[0][anims[Player].startBoardY][anims[Player].startBoardX] |= TRUE; } static void DrawSquare (int row, int column, ChessSquare piece, int do_flash) { int square_color, x, y, align=0; int i; char tString[3], bString[2]; int flash_delay; /* Calculate delay in milliseconds (2-delays per complete flash) */ flash_delay = 500 / appData.flashRate; if (flipView) { x = lineGap + ((BOARD_WIDTH-1)-column) * (squareSize + lineGap); y = lineGap + row * (squareSize + lineGap); } else { x = lineGap + column * (squareSize + lineGap); y = lineGap + ((BOARD_HEIGHT-1)-row) * (squareSize + lineGap); } square_color = SquareColor(row, column); bString[1] = bString[0] = NULLCHAR; if (appData.showCoords && row == (flipView ? BOARD_HEIGHT-1 : 0) && column >= BOARD_LEFT && column < BOARD_RGHT) { bString[0] = 'a' + column - BOARD_LEFT; align = 1; // coord in lower-right corner } if (appData.showCoords && column == (flipView ? BOARD_RGHT-1 : BOARD_LEFT)) { snprintf(tString, 3, "%d", ONE - '0' + row); align = 2; // coord in upper-left corner } if (column == (flipView ? BOARD_LEFT-1 : BOARD_RGHT) && piece > 1 ) { snprintf(tString, 3, "%d", piece); align = 3; // holdings count in upper-right corner } if (column == (flipView ? BOARD_RGHT : BOARD_LEFT-1) && piece > 1) { snprintf(tString, 3, "%d", piece); align = 4; // holdings count in upper-left corner } if(piece == DarkSquare) square_color = 2; if(square_color == 2 || appData.blindfold) piece = EmptySquare; if (do_flash && piece != EmptySquare && appData.flashCount > 0) { for (i=0; i 4) /* Castling causes 4 diffs */ return 1; } } } return 0; } /* Matrix describing castling maneuvers */ /* Row, ColRookFrom, ColKingFrom, ColRookTo, ColKingTo */ static int castling_matrix[4][5] = { { 0, 0, 4, 3, 2 }, /* 0-0-0, white */ { 0, 7, 4, 5, 6 }, /* 0-0, white */ { 7, 0, 4, 3, 2 }, /* 0-0-0, black */ { 7, 7, 4, 5, 6 } /* 0-0, black */ }; /* Checks whether castling occurred. If it did, *rrow and *rcol are set to the destination (row,col) of the rook that moved. Returns 1 if castling occurred, 0 if not. Note: Only handles a max of 1 castling move, so be sure to call too_many_diffs() first. */ static int check_castle_draw (Board newb, Board oldb, int *rrow, int *rcol) { int i, *r, j; int match; /* For each type of castling... */ for (i=0; i<4; ++i) { r = castling_matrix[i]; /* Check the 4 squares involved in the castling move */ match = 0; for (j=1; j<=4; ++j) { if (newb[r[0]][r[j]] == oldb[r[0]][r[j]]) { match = 1; break; } } if (!match) { /* All 4 changed, so it must be a castling move */ *rrow = r[0]; *rcol = r[3]; return 1; } } return 0; } void SquareExpose(int i, int j, int d) { int x, y; if (flipView) { x = lineGap + ((BOARD_WIDTH-1)-j) * (squareSize + lineGap); y = lineGap + i * (squareSize + lineGap); } else { x = lineGap + j * (squareSize + lineGap); y = lineGap + ((BOARD_HEIGHT-1)-i) * (squareSize + lineGap); } GraphExpose(currBoard, x-d, y-d, squareSize+2*d, squareSize+2*d); } void DrawPosition (int repaint, Board board) { int i, j, do_flash, exposeAll = False; static int lastFlipView = 0; static int lastBoardValid[2] = {0, 0}; static Board lastBoard[2]; static char lastMarker[BOARD_RANKS][BOARD_FILES], messedUp; int rrow = -1, rcol = -1; int nr = twoBoards*partnerUp; repaint |= messedUp; if(DrawSeekGraph()) return; // [HGM] seekgraph: suppress any drawing if seek graph up if (board == NULL) { if (!lastBoardValid[nr]) return; board = lastBoard[nr]; } if (!lastBoardValid[nr] || (nr == 0 && lastFlipView != flipView)) { MarkMenuItem("View.Flip View", flipView); } if(nr) { SlavePopUp(); SwitchWindow(0); } // [HGM] popup board if not yet popped up, and switch drawing to it. /* * It would be simpler to clear the window with XClearWindow() * but this causes a very distracting flicker. */ if (!repaint && lastBoardValid[nr] && (nr == 1 || lastFlipView == flipView)) { /* If too much changes (begin observing new game, etc.), don't do flashing */ do_flash = too_many_diffs(board, lastBoard[nr]) ? 0 : 1; /* Special check for castling so we don't flash both the king and the rook (just flash the king). */ if (do_flash) { if(check_castle_draw(board, lastBoard[nr], &rrow, &rcol)) { /* Mark rook for drawing with NO flashing. */ damage[nr][rrow][rcol] |= 1; } } /* First pass -- Erase arrow and grid highlights, but keep square content unchanged. Except for new markers. */ for (i = 0; i < BOARD_HEIGHT; i++) for (j = 0; j < BOARD_WIDTH; j++) if (damage[nr][i][j] || !nr && marker[i][j] != lastMarker[i][j]) { DrawSquare(i, j, board[i][j], 0); if(lineGap && damage[nr][i][j] & 2) { drawHighlight(j, i, 0); SquareExpose(i, j, lineGap); } else SquareExpose(i, j, 0); damage[nr][i][j] = 0; } /* Second pass -- Draw (newly) empty squares This prevents you from having a piece show up twice while it is flashing on its new square */ for (i = 0; i < BOARD_HEIGHT; i++) for (j = 0; j < BOARD_WIDTH; j++) if (board[i][j] != lastBoard[nr][i][j] && board[i][j] == EmptySquare) { DrawSquare(i, j, board[i][j], 0); SquareExpose(i, j, 0); } /* Third pass -- Draw piece(s) in new position and flash them */ for (i = 0; i < BOARD_HEIGHT; i++) for (j = 0; j < BOARD_WIDTH; j++) if (board[i][j] != lastBoard[nr][i][j]) { DrawSquare(i, j, board[i][j], do_flash && (i != rrow || j != rcol)); damage[nr][i][j] = 1; // mark for expose } } else { if (lineGap > 0) DrawGrid(); for (i = 0; i < BOARD_HEIGHT; i++) for (j = 0; j < BOARD_WIDTH; j++) { DrawSquare(i, j, board[i][j], 0); damage[nr][i][j] = False; } exposeAll = True; } CopyBoard(lastBoard[nr], board); lastBoardValid[nr] = 1; if(nr == 0) { // [HGM] dual: no highlights on second board yet lastFlipView = flipView; for (i = 0; i < BOARD_HEIGHT; i++) for (j = 0; j < BOARD_WIDTH; j++) lastMarker[i][j] = marker[i][j]; /* Draw highlights */ if (pm1X >= 0 && pm1Y >= 0) { drawHighlight(pm1X, pm1Y, 2); if(lineGap) damage[nr][pm1Y][pm1X] |= 2; } if (pm2X >= 0 && pm2Y >= 0) { drawHighlight(pm2X, pm2Y, 2); if(lineGap) damage[nr][pm2Y][pm2X] |= 2; } if (hi1X >= 0 && hi1Y >= 0) { drawHighlight(hi1X, hi1Y, 1); if(lineGap) damage[nr][hi1Y][hi1X] |= 2; } if (hi2X >= 0 && hi2Y >= 0) { drawHighlight(hi2X, hi2Y, 1); if(lineGap) damage[nr][hi2Y][hi2X] |= 2; } DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y); } else DrawArrowHighlight (board[EP_STATUS-3], board[EP_STATUS-4], board[EP_STATUS-1], board[EP_STATUS-2]); /* If piece being dragged around board, must redraw that too */ DrawDragPiece(); if(exposeAll) GraphExpose(currBoard, 0, 0, BOARD_WIDTH*(squareSize + lineGap) + lineGap, BOARD_HEIGHT*(squareSize + lineGap) + lineGap); else { for (i = 0; i < BOARD_HEIGHT; i++) for (j = 0; j < BOARD_WIDTH; j++) if(damage[nr][i][j]) { if(damage[nr][i][j] & 2) // damage by old or new arrow SquareExpose(i, j, lineGap); else SquareExpose(i, j, 0); if(nr == 0) damage[nr][i][j] = 0; // on auxiliary board we retain arrow damage } } FlashDelay(0); // this flushes drawing queue; if(nr) SwitchWindow(1); else { TimeMark now; GetTimeMark(&now); if(repaint && SubtractTimeMarks(&now, &programStartTime) < 1000) { char *p = appData.message, *q; i = 0; while(*p) { q = strchr(p, '\n'); if(q) *q = NULLCHAR; if(!strstr(appData.suppress, p)) { if(i == 0) DrawSeekBackground(2*squareSize, 3*squareSize, 6.5*squareSize, 5*squareSize); DrawText(p, 2*squareSize + 5, (int) ((3 + 0.3*i++)*squareSize) + 5, 2); } if(q) *q++ = '\n'; else q = ""; p = q; } GraphExpose(currBoard, 2*squareSize, 3*squareSize, 4*squareSize, 2*squareSize); messedUp = TRUE; } else messedUp = FALSE; } } /* [AS] Arrow highlighting support */ static double A_WIDTH = 5; /* Width of arrow body */ #define A_HEIGHT_FACTOR 6 /* Length of arrow "point", relative to body width */ #define A_WIDTH_FACTOR 3 /* Width of arrow "point", relative to body width */ static double Sqr (double x) { return x*x; } static int Round (double x) { return (int) (x + 0.5); } void SquareToPos (int rank, int file, int *x, int *y) { if (flipView) { *x = lineGap + ((BOARD_WIDTH-1)-file) * (squareSize + lineGap); *y = lineGap + rank * (squareSize + lineGap); } else { *x = lineGap + file * (squareSize + lineGap); *y = lineGap + ((BOARD_HEIGHT-1)-rank) * (squareSize + lineGap); } } /* Draw an arrow between two points using current settings */ static void DrawArrowBetweenPoints (int s_x, int s_y, int d_x, int d_y) { Pnt arrow[8]; double dx, dy, j, k, x, y; if( d_x == s_x ) { int h = (d_y > s_y) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR; arrow[0].x = s_x + A_WIDTH + 0.5; arrow[0].y = s_y; arrow[1].x = s_x + A_WIDTH + 0.5; arrow[1].y = d_y - h; arrow[2].x = arrow[1].x + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5; arrow[2].y = d_y - h; arrow[3].x = d_x; arrow[3].y = d_y; arrow[5].x = arrow[1].x - 2*A_WIDTH + 0.5; arrow[5].y = d_y - h; arrow[4].x = arrow[5].x - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5; arrow[4].y = d_y - h; arrow[6].x = arrow[1].x - 2*A_WIDTH + 0.5; arrow[6].y = s_y; } else if( d_y == s_y ) { int w = (d_x > s_x) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR; arrow[0].x = s_x; arrow[0].y = s_y + A_WIDTH + 0.5; arrow[1].x = d_x - w; arrow[1].y = s_y + A_WIDTH + 0.5; arrow[2].x = d_x - w; arrow[2].y = arrow[1].y + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5; arrow[3].x = d_x; arrow[3].y = d_y; arrow[5].x = d_x - w; arrow[5].y = arrow[1].y - 2*A_WIDTH + 0.5; arrow[4].x = d_x - w; arrow[4].y = arrow[5].y - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5; arrow[6].x = s_x; arrow[6].y = arrow[1].y - 2*A_WIDTH + 0.5; } else { /* [AS] Needed a lot of paper for this! :-) */ dy = (double) (d_y - s_y) / (double) (d_x - s_x); dx = (double) (s_x - d_x) / (double) (s_y - d_y); j = sqrt( Sqr(A_WIDTH) / (1.0 + Sqr(dx)) ); k = sqrt( Sqr(A_WIDTH*A_HEIGHT_FACTOR) / (1.0 + Sqr(dy)) ); x = s_x; y = s_y; arrow[0].x = Round(x - j); arrow[0].y = Round(y + j*dx); arrow[1].x = Round(arrow[0].x + 2*j); // [HGM] prevent width to be affected by rounding twice arrow[1].y = Round(arrow[0].y - 2*j*dx); if( d_x > s_x ) { x = (double) d_x - k; y = (double) d_y - k*dy; } else { x = (double) d_x + k; y = (double) d_y + k*dy; } x = Round(x); y = Round(y); // [HGM] make sure width of shaft is rounded the same way on both ends arrow[6].x = Round(x - j); arrow[6].y = Round(y + j*dx); arrow[2].x = Round(arrow[6].x + 2*j); arrow[2].y = Round(arrow[6].y - 2*j*dx); arrow[3].x = Round(arrow[2].x + j*(A_WIDTH_FACTOR-1)); arrow[3].y = Round(arrow[2].y - j*(A_WIDTH_FACTOR-1)*dx); arrow[4].x = d_x; arrow[4].y = d_y; arrow[5].x = Round(arrow[6].x - j*(A_WIDTH_FACTOR-1)); arrow[5].y = Round(arrow[6].y + j*(A_WIDTH_FACTOR-1)*dx); } DrawPolygon(arrow, 7); // Polygon( hdc, arrow, 7 ); } static void ArrowDamage (int s_col, int s_row, int d_col, int d_row) { int hor, vert, i, n = partnerUp * twoBoards, delta = abs(d_row - s_row); if( 2*(d_row - s_row) > abs(d_col - s_col) ) d_row = 4*d_row + 1; else if( 2*(s_row - d_row) > abs(d_col - s_col) ) d_row = 4*d_row + 3; else d_row = 4*d_row + 2; if( 2*(d_col - s_col) > delta ) d_col = 4*d_col + 1; else if( 2*(s_col - d_col) > delta ) d_col = 4*d_col + 3; else d_col = 4*d_col + 2; s_row = 4*s_row + 2; s_col = 4*s_col + 2; hor = 64*s_col; vert = 64*s_row; for(i=0; i<= 64; i++) { damage[n][vert+30>>8][hor+30>>8] |= 2; damage[n][vert-30>>8][hor+30>>8] |= 2; damage[n][vert+30>>8][hor-30>>8] |= 2; damage[n][vert-30>>8][hor-30>>8] |= 2; hor += d_col - s_col; vert += d_row - s_row; } } /* [AS] Draw an arrow between two squares */ static void DrawArrowBetweenSquares (int s_col, int s_row, int d_col, int d_row) { int s_x, s_y, d_x, d_y, delta_y; if( s_col == d_col && s_row == d_row ) { return; } /* Get source and destination points */ SquareToPos( s_row, s_col, &s_x, &s_y); SquareToPos( d_row, d_col, &d_x, &d_y); delta_y = abs(d_y - s_y); if( d_y > s_y && 2*(d_y - s_y) > abs(d_x - s_x)) { d_y += squareSize / 2 - squareSize / 4; // [HGM] round towards same centers on all sides! } else if( d_y < s_y && 2*(s_y - d_y) > abs(d_x - s_x)) { d_y += squareSize / 2 + squareSize / 4; } else { d_y += squareSize / 2; } if( d_x > s_x && 2*(d_x - s_x) > delta_y) { d_x += squareSize / 2 - squareSize / 4; } else if( d_x < s_x && 2*(s_x - d_x) > delta_y) { d_x += squareSize / 2 + squareSize / 4; } else { d_x += squareSize / 2; } s_x += squareSize / 2; s_y += squareSize / 2; /* Adjust width */ A_WIDTH = squareSize / 14.; //[HGM] make float DrawArrowBetweenPoints( s_x, s_y, d_x, d_y ); ArrowDamage(s_col, s_row, d_col, d_row); } static Boolean IsDrawArrowEnabled () { return (appData.highlightMoveWithArrow || twoBoards && partnerUp) && squareSize >= 32; } static void DrawArrowHighlight (int fromX, int fromY, int toX,int toY) { if( IsDrawArrowEnabled() && fromX >= 0 && fromY >= 0 && toX >= 0 && toY >= 0) DrawArrowBetweenSquares(fromX, fromY, toX, toY); }