From b9c6b5e9fcd6321336ec28f25833530db79b928d Mon Sep 17 00:00:00 2001 From: H.G. Muller Date: Sun, 18 Mar 2012 12:34:42 +0100 Subject: [PATCH] Split back-endish part off drawing code and move to board.c Split DrawSquare in front-end and back-end part Back-endize DrawHighlights and DrawPosition The already existing wrapper FlashDelay is used as a kludge to do an XSync. We now pass a code for the line color GC to the highight routine. New wrappers are DrawGrid() and DrawBorder(). Back-endize atomic explosions Back-endize DrawArrow and DrawDragPiece DrawDragPiece now calls DrawOneSquare with argument EmptySquare in stead of the X-type referring BlankSquare. A new wrapper DrawPolygon fixes DrawArrowBetweenPoints. Move board-drawing logic to new file board.c All X-independent drawing logic (board, squares, arrows, highlights, some animation stuff) has been extracted from xboard.c, and moved to a new file board.c, which is back-end (but unshared with WinBoard). A new header board.h defines the cross-references (which of course required some functions to be no longer declared as static). A bit of code was moved from DragPieceBegin to BeginAnimation, which now has an extra argument to indicate which piece should appear from under a dragged piece. This makes DragPieceBegin free of XCopyArea calls, so it could be moved too. Make board.c truly back-end Get rid of all X data types. For this the AnimState struct had to be redefined: the GC and Pixmap was taken out and put in front-end arrays indexed by anim agent (game and player). For this indexing a new enum was defined. The XPoint type was also replaced by our own type of an int pair (which is nasty, because the int size could be different). Move more animation code to board.c --- Makefile.am | 1 + board.c | 1186 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ board.h | 98 +++++ xboard.c | 1222 +++++------------------------------------------------------ 4 files changed, 1379 insertions(+), 1128 deletions(-) create mode 100644 board.c create mode 100644 board.h diff --git a/Makefile.am b/Makefile.am index eeadc5a..7b95775 100644 --- a/Makefile.am +++ b/Makefile.am @@ -20,6 +20,7 @@ xboard_SOURCES = backend.c backend.h backendz.h \ pgntags.c \ uci.c \ xboard.c xboard.h args.h menus.c menus.h \ + board.c board.h \ xedittags.c xedittags.h \ engineoutput.c engineoutput.h \ xengineoutput.c \ diff --git a/board.c b/board.c new file mode 100644 index 0000000..dd0e7ab --- /dev/null +++ b/board.c @@ -0,0 +1,1186 @@ +/* + * 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 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 "moves.h" +#include "board.h" + + +#ifdef __EMX__ +#ifndef HAVE_USLEEP +#define HAVE_USLEEP +#endif +#define usleep(t) _sleep2(((t)+500)/1000) +#endif + + +int squareSize, lineGap, hOffset; + +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 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); +} + +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) +{ + if (hi1X != fromX || hi1Y != fromY) { + if (hi1X >= 0 && hi1Y >= 0) { + drawHighlight(hi1X, hi1Y, 0); + } + } // [HGM] first erase both, then draw new! + + if (hi2X != toX || hi2Y != toY) { + if (hi2X >= 0 && hi2Y >= 0) { + drawHighlight(hi2X, hi2Y, 0); + } + } + if (hi1X != fromX || hi1Y != fromY) { + if (fromX >= 0 && fromY >= 0) { + drawHighlight(fromX, fromY, 1); + } + } + if (hi2X != toX || hi2Y != toY) { + if (toX >= 0 && toY >= 0) { + drawHighlight(toX, toY, 1); + } + } + + if(toX<0) // clearing the highlights must have damaged arrow + DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y); // for now, redraw it (should really be cleared!) + + 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) { + drawHighlight(pm1X, pm1Y, 0); + } + if (fromX >= 0 && fromY >= 0) { + drawHighlight(fromX, fromY, 2); + } + } + if (pm2X != toX || pm2Y != toY) { + if (pm2X >= 0 && pm2Y >= 0) { + drawHighlight(pm2X, pm2Y, 0); + } + if (toX >= 0 && toY >= 0) { + drawHighlight(toX, toY, 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; + int count, i; + AnimState *anim = &anims[anr]; + + /* Save what we are about to draw into the new buffer */ + CopyRectangle(anr, DISP, 0, + frame->x, frame->y, squareSize, 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); + } else { + /* Easy when no overlap */ + CopyRectangle(anr, 2, DISP, + 0, 0, squareSize, squareSize, + anim->prevFrame.x, anim->prevFrame.y); + } + + /* 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); +} + +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; +} + +/* 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; + Pnt start, finish, mid; + Pnt frames[kFactor * 2 + 1]; + int nFrames, startColor, endColor; + + /* 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) + 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 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, 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 string[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); + } + + if(twoBoards && partnerUp) x += hOffset; // [HGM] dual: draw second board + + square_color = SquareColor(row, column); + + string[1] = NULLCHAR; + if (appData.showCoords && row == (flipView ? BOARD_HEIGHT-1 : 0) + && column >= BOARD_LEFT && column < BOARD_RGHT) { + string[0] = 'a' + column - BOARD_LEFT; + align = 1; // coord in lower-right corner + } + if (appData.showCoords && column == (flipView ? BOARD_RGHT-1 : BOARD_LEFT)) { + string[0] = ONE + row; + align = 2; // coord in upper-left corner + } + if (column == (flipView ? BOARD_LEFT-1 : BOARD_RGHT) && piece > 1 ) { + string[0] = '0' + piece; + align = 3; // holdings count in upper-right corner + } + if (column == (flipView ? BOARD_RGHT : BOARD_LEFT-1) && piece > 1) { + string[0] = '0' + piece; + align = 4; // holdings count in upper-left corner + } + 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 +DrawPosition (int repaint, Board board) +{ + int i, j, do_flash; + static int lastFlipView = 0; + static int lastBoardValid[2] = {0, 0}; + static Board lastBoard[2]; + Arg args[16]; + int rrow, rcol; + int nr = twoBoards*partnerUp; + + 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("Flip View", flipView); + } + + /* + * 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 ( lineGap && IsDrawArrowEnabled()) + DrawGrid(0); + + /* 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)) { + /* Draw rook with NO flashing. King will be drawn flashing later */ + DrawSquare(rrow, rcol, board[rrow][rcol], 0); + lastBoard[nr][rrow][rcol] = board[rrow][rcol]; + } + } + + /* First pass -- Draw (newly) empty squares and repair damage. + 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) + || damage[nr][i][j]) { + DrawSquare(i, j, board[i][j], 0); + damage[nr][i][j] = False; + } + + /* Second 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); + } + } else { + if (lineGap > 0) + DrawGrid(twoBoards & partnerUp); + + 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; + } + } + + CopyBoard(lastBoard[nr], board); + lastBoardValid[nr] = 1; + if(nr == 0) { // [HGM] dual: no highlights on second board yet + lastFlipView = flipView; + + /* Draw highlights */ + if (pm1X >= 0 && pm1Y >= 0) { + drawHighlight(pm1X, pm1Y, 2); + } + if (pm2X >= 0 && pm2Y >= 0) { + drawHighlight(pm2X, pm2Y, 2); + } + if (hi1X >= 0 && hi1Y >= 0) { + drawHighlight(hi1X, hi1Y, 1); + } + if (hi2X >= 0 && hi2Y >= 0) { + drawHighlight(hi2X, hi2Y, 1); + } + DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y); + } + /* If piece being dragged around board, must redraw that too */ + DrawDragPiece(); + + FlashDelay(0); // this flushes drawing queue; +} + +/* [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 */ +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 ); +} + +void +ArrowDamage (int s_col, int s_row, int d_col, int d_row) +{ + int hor, vert, i; + hor = 64*s_col + 32; vert = 64*s_row + 32; + for(i=0; i<= 64; i++) { + damage[0][vert+6>>6][hor+6>>6] = True; + damage[0][vert-6>>6][hor+6>>6] = True; + damage[0][vert+6>>6][hor-6>>6] = True; + damage[0][vert-6>>6][hor-6>>6] = True; + hor += d_col - s_col; vert += d_row - s_row; + } +} + +/* [AS] Draw an arrow between two squares */ +void +DrawArrowBetweenSquares (int s_col, int s_row, int d_col, int d_row) +{ + int s_x, s_y, d_x, d_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); + + if( d_y > s_y ) { + d_y += squareSize / 2 - squareSize / 4; // [HGM] round towards same centers on all sides! + } + else if( d_y < s_y ) { + d_y += squareSize / 2 + squareSize / 4; + } + else { + d_y += squareSize / 2; + } + + if( d_x > s_x ) { + d_x += squareSize / 2 - squareSize / 4; + } + else if( d_x < s_x ) { + 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); +} + +Boolean +IsDrawArrowEnabled () +{ + return appData.highlightMoveWithArrow && squareSize >= 32; +} + +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); +} + + diff --git a/board.h b/board.h new file mode 100644 index 0000000..47d8329 --- /dev/null +++ b/board.h @@ -0,0 +1,98 @@ +/* + * board.h -- header for XBoard: variables shared by xboard.c and board.c + * + * Copyright 1991 by Digital Equipment Corporation, Maynard, + * Massachusetts. + * + * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006, + * 2007, 2008, 2009, 2010, 2011, 2012 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. */ + + +/* This magic number is the number of intermediate frames used + in each half of the animation. For short moves it's reduced + by 1. The total number of frames will be factor * 2 + 1. */ +#define kFactor 4 + +/* Variables for doing smooth animation. This whole thing + would be much easier if the board was double-buffered, + but that would require a fairly major rewrite. */ + +#define DISP 4 + +typedef enum { Game=0, Player, NrOfAnims } AnimNr; + +typedef struct { + short int x, y; + } Pnt; + +typedef struct { + Pnt startSquare, prevFrame, mouseDelta; + int startColor; + int dragPiece; + Boolean dragActive; + int startBoardX, startBoardY; + } AnimState; + +extern AnimState anims[]; + +void DrawPolygon P((Pnt arrow[], int nr)); +void DrawOneSquare P((int x, int y, ChessSquare piece, int square_color, int marker, char *string, int align)); +void DrawDot P((int marker, int x, int y, int r)); +void DrawGrid P((int second)); +int SquareColor P((int row, int column)); +void ScreenSquare P((int column, int row, Pnt *pt, int *color)); +void BoardSquare P((int x, int y, int *column, int *row)); +void FrameDelay P((int time)); +void InsertPiece P((AnimNr anr, ChessSquare piece)); +void DrawBlank P((AnimNr anr, int x, int y, int startColor)); +void CopyRectangle P((AnimNr anr, int srcBuf, int destBuf, int srcX, int srcY, int width, int height, int destX, int destY)); +void SetDragPiece P((AnimNr anr, ChessSquare piece)); +void DragPieceMove P((int x, int y)); +void DrawArrowHighlight P((int fromX, int fromY, int toX,int toY)); +Boolean IsDrawArrowEnabled P((void)); + +extern int hOffset; // [HGM] dual +extern int damage[2][BOARD_RANKS][BOARD_FILES]; + diff --git a/xboard.c b/xboard.c index ec7568c..934f903 100644 --- a/xboard.c +++ b/xboard.c @@ -204,6 +204,7 @@ extern char *getenv(); #include "xhistory.h" #include "xedittags.h" #include "menus.h" +#include "board.h" #include "gettext.h" @@ -251,7 +252,6 @@ void ReadBitmap P((Pixmap *pm, String name, unsigned char bits[], u_int wreq, u_int hreq)); void CreateGrid P((void)); int EventToSquare P((int x, int limit)); -void DrawSquare P((int row, int column, ChessSquare piece, int do_flash)); void EventProc P((Widget widget, caddr_t unused, XEvent *event)); void DelayedDrag P((void)); void MoveTypeInProc P((Widget widget, caddr_t unused, XEvent *event)); @@ -311,8 +311,6 @@ void ICSInitScript P((void)); void ErrorPopUp P((char *title, char *text, int modal)); void ErrorPopDown P((void)); static char *ExpandPathName P((char *path)); -static void DragPieceMove P((int x, int y)); -static void DrawDragPiece P((void)); void SelectMove P((Widget w, XEvent * event, String * params, Cardinal * nParams)); void GameListOptionsPopDown P(()); void GenericPopDown P(()); @@ -343,7 +341,6 @@ Widget shellWidget, layoutWidget, formWidget, boardWidget, messageWidget, menuBarWidget, buttonBarWidget, editShell, errorShell, analysisShell, ICSInputShell, fileNameShell, askQuestionShell; Widget historyShell, evalGraphShell, gameListShell; -int hOffset; // [HGM] dual XSegment secondSegments[BOARD_RANKS + BOARD_FILES + 2]; XSegment gridSegments[BOARD_RANKS + BOARD_FILES + 2]; XSegment jailGridSegments[BOARD_RANKS + BOARD_FILES + 6]; @@ -370,12 +367,12 @@ BoardSize boardSize; Boolean chessProgram; int minX, minY; // [HGM] placement: volatile limits on upper-left corner -int squareSize, smallLayout = 0, tinyLayout = 0, +int smallLayout = 0, tinyLayout = 0, marginW, marginH, // [HGM] for run-time resizing fromX = -1, fromY = -1, toX, toY, commentUp = False, analysisUp = False, ICSInputBoxUp = False, askQuestionUp = False, filenameUp = False, promotionUp = False, pmFromX = -1, pmFromY = -1, - errorUp = False, errorExitStatus = -1, lineGap, defaultLineGap; + errorUp = False, errorExitStatus = -1, defaultLineGap; Dimension textHeight; Pixel timerForegroundPixel, timerBackgroundPixel; Pixel buttonForegroundPixel, buttonBackgroundPixel; @@ -418,26 +415,6 @@ XImage *xim_Cross; #define White(piece) ((int)(piece) < (int)BlackPawn) -/* Variables for doing smooth animation. This whole thing - would be much easier if the board was double-buffered, - but that would require a fairly major rewrite. */ - -typedef struct { - Pixmap saveBuf; - Pixmap newBuf; - GC blitGC, pieceGC, outlineGC; - XPoint startSquare, prevFrame, mouseDelta; - int startColor; - int dragPiece; - Boolean dragActive; - int startBoardX, startBoardY; - } AnimState; - -/* There can be two pieces being animated at once: a player - can begin dragging a piece before the remote opponent has moved. */ - -static AnimState game, player; - /* Bitmaps for use as masks when drawing XPM pieces. Need one for each black and white piece. */ static Pixmap xpmMask[BlackKing + 1]; @@ -3354,128 +3331,23 @@ BlackClock (Widget w, XEvent *event, String *prms, Cardinal *nprms) } -/* - * 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; -} - static void do_flash_delay (unsigned long msec) { TimeDelay(msec); } -static void -drawHighlight (int file, int rank, GC gc) +void +DrawBorder (int x, int y, int type) { - int x, y; + GC gc = lineGC; - 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); - } + if(type == 1) gc = highlineGC; else if(type == 2) gc = prelineGC; XDrawRectangle(xDisplay, xBoardWindow, gc, x, y, squareSize+lineGap, squareSize+lineGap); } -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) -{ - if (hi1X != fromX || hi1Y != fromY) { - if (hi1X >= 0 && hi1Y >= 0) { - drawHighlight(hi1X, hi1Y, lineGC); - } - } // [HGM] first erase both, then draw new! - - if (hi2X != toX || hi2Y != toY) { - if (hi2X >= 0 && hi2Y >= 0) { - drawHighlight(hi2X, hi2Y, lineGC); - } - } - if (hi1X != fromX || hi1Y != fromY) { - if (fromX >= 0 && fromY >= 0) { - drawHighlight(fromX, fromY, highlineGC); - } - } - if (hi2X != toX || hi2Y != toY) { - if (toX >= 0 && toY >= 0) { - drawHighlight(toX, toY, highlineGC); - } - } - - if(toX<0) // clearing the highlights must have damaged arrow - DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y); // for now, redraw it (should really be cleared!) - - 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) { - drawHighlight(pm1X, pm1Y, lineGC); - } - if (fromX >= 0 && fromY >= 0) { - drawHighlight(fromX, fromY, prelineGC); - } - } - if (pm2X != toX || pm2Y != toY) { - if (pm2X >= 0 && pm2Y >= 0) { - drawHighlight(pm2X, pm2Y, lineGC); - } - if (toX >= 0 && toY >= 0) { - drawHighlight(toX, toY, prelineGC); - } - } - pm1X = fromX; - pm1Y = fromY; - pm2X = toX; - pm2Y = toY; -} - -void -ClearPremoveHighlights () -{ - SetPremoveHighlights(-1, -1, -1, -1); -} - static int CutOutSquare (int x, int y, int *x0, int *y0, int kind) { @@ -3672,154 +3544,71 @@ ChooseDrawFunc () } } -/* [HR] determine square color depending on chess variant. */ -static 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; - - return square_color; +void +DrawDot (int marker, int x, int y, int r) +{ + if(appData.monoMode) { + XFillArc(xDisplay, xBoardWindow, marker == 2 ? darkSquareGC : lightSquareGC, + x, y, r, r, 0, 64*360); + XDrawArc(xDisplay, xBoardWindow, marker == 2 ? lightSquareGC : darkSquareGC, + x, y, r, r, 0, 64*360); + } else + XFillArc(xDisplay, xBoardWindow, marker == 2 ? prelineGC : highlineGC, + x, y, r, r, 0, 64*360); } void -DrawSquare (int row, int column, ChessSquare piece, int do_flash) -{ - int square_color, x, y, direction, font_ascent, font_descent; - int i; - char string[2]; +DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *string, int align) +{ // basic front-end board-draw function: takes care of everything that can be in square: + // piece, background, coordinate/count, marker dot + int direction, font_ascent, font_descent; XCharStruct overall; DrawFunc drawfunc; - 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); + if (piece == EmptySquare) { + BlankSquare(x, y, square_color, piece, xBoardWindow, 1); } else { - x = lineGap + column * (squareSize + lineGap); - y = lineGap + ((BOARD_HEIGHT-1)-row) * - (squareSize + lineGap); - } - - if(twoBoards && partnerUp) x += hOffset; // [HGM] dual: draw second board - - square_color = SquareColor(row, column); - - 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) ) { - BlankSquare(x, y, 2, EmptySquare, xBoardWindow, 1); - - // [HGM] print piece counts next to holdings - string[1] = NULLCHAR; - if (column == (flipView ? BOARD_LEFT-1 : BOARD_RGHT) && piece > 1 ) { - string[0] = '0' + piece; - XTextExtents(countFontStruct, string, 1, &direction, - &font_ascent, &font_descent, &overall); - if (appData.monoMode) { - XDrawImageString(xDisplay, xBoardWindow, countGC, - x + squareSize - overall.width - 2, - y + font_ascent + 1, string, 1); - } else { - XDrawString(xDisplay, xBoardWindow, countGC, - x + squareSize - overall.width - 2, - y + font_ascent + 1, string, 1); - } - } - if (column == (flipView ? BOARD_RGHT : BOARD_LEFT-1) && piece > 1) { - string[0] = '0' + piece; - XTextExtents(countFontStruct, string, 1, &direction, - &font_ascent, &font_descent, &overall); - if (appData.monoMode) { - XDrawImageString(xDisplay, xBoardWindow, countGC, - x + 2, y + font_ascent + 1, string, 1); - } else { - XDrawString(xDisplay, xBoardWindow, countGC, - x + 2, y + font_ascent + 1, string, 1); - } - } - } else { - if (piece == EmptySquare || appData.blindfold) { - BlankSquare(x, y, square_color, piece, xBoardWindow, 1); - } else { - drawfunc = ChooseDrawFunc(); - - if (do_flash && appData.flashCount > 0) { - for (i=0; i= BOARD_LEFT && column < BOARD_RGHT) { - string[0] = 'a' + column - BOARD_LEFT; - XTextExtents(coordFontStruct, string, 1, &direction, - &font_ascent, &font_descent, &overall); - if (appData.monoMode) { - XDrawImageString(xDisplay, xBoardWindow, coordGC, - x + squareSize - overall.width - 2, - y + squareSize - font_descent - 1, string, 1); - } else { - XDrawString(xDisplay, xBoardWindow, coordGC, - x + squareSize - overall.width - 2, - y + squareSize - font_descent - 1, string, 1); + drawfunc = ChooseDrawFunc(); + drawfunc(piece, square_color, x, y, xBoardWindow); + } + + if(align) { // square carries inscription (coord or piece count) + int xx = x, yy = y; + GC hGC = align < 3 ? coordGC : countGC; + // first calculate where it goes + XTextExtents(countFontStruct, string, 1, &direction, + &font_ascent, &font_descent, &overall); + if (align == 1) { + xx += squareSize - overall.width - 2; + yy += squareSize - font_descent - 1; + } else if (align == 2) { + xx += 2, yy += font_ascent + 1; + } else if (align == 3) { + xx += squareSize - overall.width - 2; + yy += font_ascent + 1; + } else if (align == 4) { + xx += 2, yy += font_ascent + 1; } - } - if (appData.showCoords && column == (flipView ? BOARD_RGHT-1 : BOARD_LEFT)) { - string[0] = ONE + row; - XTextExtents(coordFontStruct, string, 1, &direction, - &font_ascent, &font_descent, &overall); + // then draw it if (appData.monoMode) { - XDrawImageString(xDisplay, xBoardWindow, coordGC, - x + 2, y + font_ascent + 1, string, 1); + XDrawImageString(xDisplay, xBoardWindow, hGC, xx, yy, string, 1); } else { - XDrawString(xDisplay, xBoardWindow, coordGC, - x + 2, y + font_ascent + 1, string, 1); + XDrawString(xDisplay, xBoardWindow, hGC, xx, yy, string, 1); } } - if(!partnerUp && marker[row][column]) { - if(appData.monoMode) { - XFillArc(xDisplay, xBoardWindow, marker[row][column] == 2 ? darkSquareGC : lightSquareGC, - x + squareSize/4, y+squareSize/4, squareSize/2, squareSize/2, 0, 64*360); - XDrawArc(xDisplay, xBoardWindow, marker[row][column] == 2 ? lightSquareGC : darkSquareGC, - x + squareSize/4, y+squareSize/4, squareSize/2, squareSize/2, 0, 64*360); - } else - XFillArc(xDisplay, xBoardWindow, marker[row][column] == 2 ? prelineGC : highlineGC, - x + squareSize/4, y+squareSize/4, squareSize/2, squareSize/2, 0, 64*360); + + if(marker) { // print fat marker dot, if requested + DrawDot(marker, x + squareSize/4, y+squareSize/4, squareSize/2); } } +void +FlashDelay (int flash_delay) +{ + XSync(xDisplay, False); + if(flash_delay) do_flash_delay(flash_delay); +} + double Fraction (int x, int start, int stop) { @@ -3879,7 +3668,7 @@ DragProc () if(EvalGraphIsUp()) CoDrag(evalGraphShell, &wpEvalGraph); if(GameListIsUp()) CoDrag(gameListShell, &wpGameList); wpMain = wpNew; - XDrawPosition(boardWidget, True, NULL); + DrawPosition(True, NULL); delayedDragID = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other) } @@ -3905,10 +3694,10 @@ EventProc (Widget widget, caddr_t unused, XEvent *event) break; case Expose: if (event->xexpose.count > 0) return; /* no clipping is done */ - XDrawPosition(widget, True, NULL); + DrawPosition(True, NULL); if(twoBoards) { // [HGM] dual: draw other board in other orientation flipView = !flipView; partnerUp = !partnerUp; - XDrawPosition(widget, True, NULL); + DrawPosition(True, NULL); flipView = !flipView; partnerUp = !partnerUp; } break; @@ -3920,77 +3709,6 @@ EventProc (Widget widget, caddr_t unused, XEvent *event) } /* end why */ -void -DrawPosition (int fullRedraw, Board board) -{ - XDrawPosition(boardWidget, fullRedraw, board); -} - -/* Returns 1 if there are "too many" differences between b1 and b2 - (i.e. more than 1 move was made) */ -static int -too_many_diffs (Board b1, Board b2) -{ - int i, j; - int c = 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; -} - // [HGM] seekgraph: some low-level drawing routines cloned from xevalgraph void DrawSeekAxis (int x, int y, int xTo, int yTo) @@ -4025,111 +3743,12 @@ DrawSeekDot (int x, int y, int colorNr) x-squareSize/8, y-squareSize/8, squareSize/4, squareSize/4, 0, 64*360); } -static int damage[2][BOARD_RANKS][BOARD_FILES]; - -/* - * event handler for redrawing the board - */ void -XDrawPosition (Widget w, int repaint, Board board) +DrawGrid (int second) { - int i, j, do_flash; - static int lastFlipView = 0; - static int lastBoardValid[2] = {0, 0}; - static Board lastBoard[2]; - Arg args[16]; - int rrow, rcol; - int nr = twoBoards*partnerUp; - - 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("Flip View", flipView); - } - - /* - * 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 ( lineGap && IsDrawArrowEnabled()) - XDrawSegments(xDisplay, xBoardWindow, lineGC, - gridSegments, BOARD_HEIGHT + BOARD_WIDTH + 2); - - /* 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)) { - /* Draw rook with NO flashing. King will be drawn flashing later */ - DrawSquare(rrow, rcol, board[rrow][rcol], 0); - lastBoard[nr][rrow][rcol] = board[rrow][rcol]; - } - } - - /* First pass -- Draw (newly) empty squares and repair damage. - 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) - || damage[nr][i][j]) { - DrawSquare(i, j, board[i][j], 0); - damage[nr][i][j] = False; - } - - /* Second 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); - } - } else { - if (lineGap > 0) XDrawSegments(xDisplay, xBoardWindow, lineGC, - twoBoards & partnerUp ? secondSegments : // [HGM] dual + second ? secondSegments : // [HGM] dual gridSegments, BOARD_HEIGHT + BOARD_WIDTH + 2); - - 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; - } - } - - CopyBoard(lastBoard[nr], board); - lastBoardValid[nr] = 1; - if(nr == 0) { // [HGM] dual: no highlights on second board yet - lastFlipView = flipView; - - /* Draw highlights */ - if (pm1X >= 0 && pm1Y >= 0) { - drawHighlight(pm1X, pm1Y, prelineGC); - } - if (pm2X >= 0 && pm2Y >= 0) { - drawHighlight(pm2X, pm2Y, prelineGC); - } - if (hi1X >= 0 && hi1Y >= 0) { - drawHighlight(hi1X, hi1Y, highlineGC); - } - if (hi2X >= 0 && hi2Y >= 0) { - drawHighlight(hi2X, hi2Y, highlineGC); - } - DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y); - } - /* If piece being dragged around board, must redraw that too */ - DrawDragPiece(); - - XSync(xDisplay, False); } @@ -4139,7 +3758,7 @@ XDrawPosition (Widget w, int repaint, Board board) void DrawPositionProc (Widget w, XEvent *event, String *prms, Cardinal *nprms) { - XDrawPosition(w, True, NULL); + DrawPosition(True, NULL); } @@ -6067,18 +5686,7 @@ OutputToProcessDelayed (ProcRef pr, char *message, int count, int *outError, lon return outCount; } -/**** 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. -****/ +/**** Animation code by Hugh Fisher, DCS, ANU. ****/ /* Masks for XPM pieces. Black and white pieces can have different shapes, but in the interest of retaining my @@ -6087,6 +5695,8 @@ OutputToProcessDelayed (ProcRef pr, char *message, int count, int *outError, lon background square colors/images. */ static int xpmDone = 0; +static Pixmap animBufs[3*NrOfAnims]; // newBuf, saveBuf +static GC animGCs[3*NrOfAnims]; // blitGC, pieceGC, outlineGC; static void CreateAnimMasks (int pieceDepth) @@ -6167,15 +5777,16 @@ CreateAnimMasks (int pieceDepth) } static void -InitAnimState (AnimState *anim, XWindowAttributes *info) +InitAnimState (AnimNr anr, XWindowAttributes *info) { XtGCMask mask; XGCValues values; /* Each buffer is square size, same depth as window */ - anim->saveBuf = XCreatePixmap(xDisplay, xBoardWindow, + animBufs[anr+4] = xBoardWindow; + animBufs[anr+2] = XCreatePixmap(xDisplay, xBoardWindow, squareSize, squareSize, info->depth); - anim->newBuf = XCreatePixmap(xDisplay, xBoardWindow, + animBufs[anr] = XCreatePixmap(xDisplay, xBoardWindow, squareSize, squareSize, info->depth); /* Create a plain GC for blitting */ @@ -6186,14 +5797,14 @@ InitAnimState (AnimState *anim, XWindowAttributes *info) values.function = GXcopy; values.plane_mask = AllPlanes; values.graphics_exposures = False; - anim->blitGC = XCreateGC(xDisplay, xBoardWindow, mask, &values); + animGCs[anr] = XCreateGC(xDisplay, xBoardWindow, mask, &values); /* Piece will be copied from an existing context at the start of each new animation/drag. */ - anim->pieceGC = XCreateGC(xDisplay, xBoardWindow, 0, &values); + animGCs[anr+2] = XCreateGC(xDisplay, xBoardWindow, 0, &values); /* Outline will be a read-only copy of an existing */ - anim->outlineGC = None; + animGCs[anr+4] = None; } void @@ -6205,8 +5816,8 @@ CreateAnimVars () if(xpmDone) oldVariant = gameInfo.variant; // first time pieces might not be created yet XGetWindowAttributes(xDisplay, xBoardWindow, &info); - InitAnimState(&game, &info); - InitAnimState(&player, &info); + InitAnimState(Game, &info); + InitAnimState(Player, &info); /* For XPM pieces, we need bitmaps to use as masks. */ if (useImages) @@ -6225,7 +5836,7 @@ FrameAlarm (int sig) signal(SIGALRM, FrameAlarm); } -static void +void FrameDelay (int time) { struct itimerval delay; @@ -6249,7 +5860,7 @@ FrameDelay (int time) #else -static void +void FrameDelay (int time) { XSync(xDisplay, False); @@ -6259,159 +5870,6 @@ FrameDelay (int time) #endif -void -DoSleep (int n) -{ - FrameDelay(n); -} - -/* Convert board position to corner of screen rect and color */ - -static void -ScreenSquare (int column, int row, XPoint *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 */ - -static 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; -} - -/* Utilities */ - -#undef Max /* just in case */ -#undef Min -#define Max(a, b) ((a) > (b) ? (a) : (b)) -#define Min(a, b) ((a) < (b) ? (a) : (b)) - -static void -SetRect (XRectangle *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 ( XPoint *old, XPoint *new, int size, XRectangle *area, XPoint *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 (XPoint *old, XPoint *new, int size, XRectangle 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; -} - -/* 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 (XPoint *start, XPoint *mid, XPoint *finish, int factor, XPoint 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; -} - -/* Draw a piece on the screen without disturbing what's there */ - static void SelectGCMask (ChessSquare piece, GC *clip, GC *outline, Pixmap *mask) { @@ -6480,329 +5938,32 @@ OverlayPiece (ChessSquare piece, GC clip, GC outline, Drawable dest) } } -/* Animate the movement of a single piece */ - -static void -BeginAnimation (AnimState *anim, ChessSquare piece, int startColor, XPoint *start) -{ - Pixmap mask; - - if(appData.upsideDown && flipView) piece += piece < BlackPawn ? BlackPawn : -BlackPawn; - /* The old buffer is initialised with the start square (empty) */ - BlankSquare(start->x, start->y, startColor, EmptySquare, anim->saveBuf, 0); - anim->prevFrame = *start; - - /* The piece will be drawn using its own bitmap as a matte */ - SelectGCMask(piece, &anim->pieceGC, &anim->outlineGC, &mask); - XSetClipMask(xDisplay, anim->pieceGC, mask); -} - -static void -AnimationFrame (AnimState *anim, XPoint *frame, ChessSquare piece) -{ - XRectangle updates[4]; - XRectangle overlap; - XPoint pt; - int count, i; - - /* Save what we are about to draw into the new buffer */ - XCopyArea(xDisplay, xBoardWindow, anim->newBuf, anim->blitGC, - frame->x, frame->y, squareSize, 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. */ - XCopyArea(xDisplay, anim->saveBuf, anim->newBuf, anim->blitGC, - 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++) - XCopyArea(xDisplay, anim->saveBuf, xBoardWindow, anim->blitGC, - 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 { - /* Easy when no overlap */ - XCopyArea(xDisplay, anim->saveBuf, xBoardWindow, anim->blitGC, - 0, 0, squareSize, squareSize, - anim->prevFrame.x, anim->prevFrame.y); - } - - /* Save this frame for next time round */ - XCopyArea(xDisplay, anim->newBuf, anim->saveBuf, anim->blitGC, - 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. */ - OverlayPiece(piece, anim->pieceGC, anim->outlineGC, anim->newBuf); - XCopyArea(xDisplay, anim->newBuf, xBoardWindow, anim->blitGC, - 0, 0, squareSize, squareSize, - frame->x, frame->y); -} - -static void -EndAnimation (AnimState *anim, XPoint *finish) -{ - XRectangle updates[4]; - XRectangle overlap; - XPoint pt; - int count, i; - - /* 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++) - XCopyArea(xDisplay, anim->saveBuf, xBoardWindow, anim->blitGC, - 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 { - XCopyArea(xDisplay, anim->saveBuf, xBoardWindow, anim->blitGC, - 0, 0, squareSize, squareSize, - anim->prevFrame.x, anim->prevFrame.y); - } -} - -static void -FrameSequence (AnimState *anim, ChessSquare piece, int startColor, XPoint *start, XPoint *finish, XPoint frames[], int nFrames) -{ - int n; - - BeginAnimation(anim, piece, startColor, start); - for (n = 0; n < nFrames; n++) { - AnimationFrame(anim, &(frames[n]), piece); - FrameDelay(appData.animSpeed); - } - EndAnimation(anim, finish); -} - void -AnimateAtomicCapture (Board board, int fromX, int fromY, int toX, int toY) +InsertPiece (AnimNr anr, ChessSquare piece) { - 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); - XFillArc(xDisplay, xBoardWindow, highlineGC, - x + squareSize/2 - r, y+squareSize/2 - r, 2*r, 2*r, 0, 64*360); - FrameDelay(appData.animSpeed); - } - board[fromY][toY] = piece; + OverlayPiece(piece, animGCs[anr+2], animGCs[anr+4], animBufs[anr]); } -/* Main control logic for deciding what to animate and how */ - void -AnimateMove (Board board, int fromX, int fromY, int toX, int toY) +DrawBlank (AnimNr anr, int x, int y, int startColor) { - ChessSquare piece; - int hop; - XPoint start, finish, mid; - XPoint frames[kFactor * 2 + 1]; - int nFrames, startColor, endColor; - - /* 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) - 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 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 && player.dragPiece < EmptySquare) { - player.dragActive = True; - BeginAnimation(&player, player.dragPiece, 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. */ - if(boardX == BOARD_RGHT+1 && PieceForSquare(boardX-1, boardY) > 1 || - boardX == BOARD_LEFT-2 && PieceForSquare(boardX+1, boardY) > 1) - XCopyArea(xDisplay, xBoardWindow, player.saveBuf, player.blitGC, - corner.x, corner.y, squareSize, squareSize, - 0, 0); // [HGM] zh: unstack in stead of grab - if(gatingPiece != EmptySquare) { - /* 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. */ - ChessSquare piece = boards[currentMove][boardY][boardX]; - DrawSquare(boardY, boardX, gatingPiece, 0); - XCopyArea(xDisplay, xBoardWindow, player.saveBuf, player.blitGC, - corner.x, corner.y, squareSize, squareSize, 0, 0); - DrawSquare(boardY, boardX, piece, 0); - } - damage[0][boardY][boardX] = True; - } else { - player.dragActive = False; - } + XCopyArea(xDisplay, animBufs[anr+srcBuf], animBufs[anr+destBuf], animGCs[anr], + srcX, srcY, width, height, destX, destY); } void -ChangeDragPiece (ChessSquare piece) +SetDragPiece (AnimNr anr, ChessSquare piece) { Pixmap mask; - player.dragPiece = piece; /* The piece will be drawn using its own bitmap as a matte */ - SelectGCMask(piece, &player.pieceGC, &player.outlineGC, &mask); - XSetClipMask(xDisplay, player.pieceGC, mask); -} - -static void -DragPieceMove (int x, int y) -{ - XPoint corner; - - /* Are we animating? */ - if (!appData.animateDragging || appData.blindfold) - return; - - /* Sanity check */ - if (! player.dragActive) - return; - /* Move piece, maintaining same relative position - of mouse within square */ - corner.x = x - player.mouseDelta.x; - corner.y = y - player.mouseDelta.y; - AnimationFrame(&player, &corner, player.dragPiece); -#if HIGHDRAG*0 - if (appData.highlightDragging) { - int boardX, boardY; - BoardSquare(x, y, &boardX, &boardY); - SetHighlights(fromX, fromY, boardX, boardY); - } -#endif -} - -void -DragPieceEnd (int x, int y) -{ - int boardX, boardY, color; - XPoint corner; - - /* Are we animating? */ - if (!appData.animateDragging || appData.blindfold) - return; - - /* Sanity check */ - if (! player.dragActive) - return; - /* Last frame in sequence is square piece is - placed on, which may not match mouse exactly. */ - BoardSquare(x, y, &boardX, &boardY); - ScreenSquare(boardX, boardY, &corner, &color); - EndAnimation(&player, &corner); - - /* Be sure end square is redrawn */ - damage[0][boardY][boardX] = True; - - /* This prevents weird things happening with fast successive - clicks which on my Sun at least can cause motion events - without corresponding press/release. */ - player.dragActive = False; -} - -/* Handle expose event while piece being dragged */ - -static void -DrawDragPiece () -{ - if (!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. */ - BlankSquare(player.startSquare.x, player.startSquare.y, - player.startColor, EmptySquare, xBoardWindow, 1); - AnimationFrame(&player, &player.prevFrame, player.dragPiece); - damage[0][player.startBoardY][player.startBoardX] = TRUE; + SelectGCMask(piece, &animGCs[anr+2], &animGCs[anr+4], &mask); + XSetClipMask(xDisplay, animGCs[anr+2], mask); } #include @@ -6845,209 +6006,14 @@ NotifyFrontendLogin () /* [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 */ -void -DrawArrowBetweenPoints (int s_x, int s_y, int d_x, int d_y) -{ - XPoint 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); - } - - XFillPolygon(xDisplay, xBoardWindow, highlineGC, arrow, 7, Nonconvex, CoordModeOrigin); - if(appData.monoMode) arrow[7] = arrow[0], XDrawLines(xDisplay, xBoardWindow, darkSquareGC, arrow, 8, CoordModeOrigin); -// Polygon( hdc, arrow, 7 ); -} - -void -ArrowDamage (int s_col, int s_row, int d_col, int d_row) -{ - int hor, vert, i; - hor = 64*s_col + 32; vert = 64*s_row + 32; - for(i=0; i<= 64; i++) { - damage[0][vert+6>>6][hor+6>>6] = True; - damage[0][vert-6>>6][hor+6>>6] = True; - damage[0][vert+6>>6][hor-6>>6] = True; - damage[0][vert-6>>6][hor-6>>6] = True; - hor += d_col - s_col; vert += d_row - s_row; - } -} - -/* [AS] Draw an arrow between two squares */ -void -DrawArrowBetweenSquares (int s_col, int s_row, int d_col, int d_row) -{ - int s_x, s_y, d_x, d_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); - - if( d_y > s_y ) { - d_y += squareSize / 2 - squareSize / 4; // [HGM] round towards same centers on all sides! - } - else if( d_y < s_y ) { - d_y += squareSize / 2 + squareSize / 4; - } - else { - d_y += squareSize / 2; - } - - if( d_x > s_x ) { - d_x += squareSize / 2 - squareSize / 4; - } - else if( d_x < s_x ) { - 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); -} - -Boolean -IsDrawArrowEnabled () -{ - return appData.highlightMoveWithArrow && squareSize >= 32; -} - void -DrawArrowHighlight (int fromX, int fromY, int toX,int toY) +DrawPolygon (Pnt arrow[], int nr) { - if( IsDrawArrowEnabled() && fromX >= 0 && fromY >= 0 && toX >= 0 && toY >= 0) - DrawArrowBetweenSquares(fromX, fromY, toX, toY); + XPoint pts[10]; + int i; + for(i=0; i<10; i++) pts[i].x = arrow[i].x, pts[i].y = arrow[i].y; + XFillPolygon(xDisplay, xBoardWindow, highlineGC, pts, nr, Nonconvex, CoordModeOrigin); + if(appData.monoMode) arrow[nr] = arrow[0], XDrawLines(xDisplay, xBoardWindow, darkSquareGC, pts, nr+1, CoordModeOrigin); } void -- 1.7.0.4