Split back-endish part off drawing code and move to board.c
authorH.G. Muller <h.g.muller@hccnet.nl>
Sun, 18 Mar 2012 11:34:42 +0000 (12:34 +0100)
committerH.G. Muller <h.g.muller@hccnet.nl>
Tue, 10 Apr 2012 09:31:45 +0000 (11:31 +0200)
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
board.c [new file with mode: 0644]
board.h [new file with mode: 0644]
xboard.c

index eeadc5a..7b95775 100644 (file)
@@ -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 (file)
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 <stdio.h>
+#include <ctype.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <pwd.h>
+#include <math.h>
+
+#if STDC_HEADERS
+# include <stdlib.h>
+# include <string.h>
+#else /* not STDC_HEADERS */
+extern char *getenv();
+# if HAVE_STRING_H
+#  include <string.h>
+# else /* not HAVE_STRING_H */
+#  include <strings.h>
+# endif /* not HAVE_STRING_H */
+#endif /* not STDC_HEADERS */
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#if HAVE_SYS_WAIT_H
+# include <sys/wait.h>
+#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<BOARD_WIDTH; i++) for(j=0; j<BOARD_HEIGHT; j++)
+      if((i-toX)*(i-toX) + (j-toY)*(j-toY) < 6) damage[0][j][i] = True;
+  }
+
+  /* Be sure end square is redrawn */
+  damage[0][toY][toX] = True;
+}
+
+void
+ChangeDragPiece (ChessSquare piece)
+{
+  anims[Player].dragPiece = piece;
+  SetDragPiece(Player, piece);
+}
+
+void
+DragPieceMove (int x, int y)
+{
+    Pnt corner;
+
+    /* Are we animating? */
+    if (!appData.animateDragging || appData.blindfold)
+      return;
+
+    /* Sanity check */
+    if (! anims[Player].dragActive)
+      return;
+    /* Move piece, maintaining same relative position
+       of mouse within square   */
+    corner.x = x - anims[Player].mouseDelta.x;
+    corner.y = y - anims[Player].mouseDelta.y;
+    AnimationFrame(Player, &corner, anims[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;
+    Pnt corner;
+
+    /* Are we animating? */
+    if (!appData.animateDragging || appData.blindfold)
+      return;
+
+    /* Sanity check */
+    if (! anims[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. */
+    anims[Player].dragActive = False;
+}
+
+void
+DragPieceBegin (int x, int y, Boolean instantly)
+{
+    int         boardX, boardY, color;
+    Pnt corner;
+
+    /* Are we animating? */
+    if (!appData.animateDragging || appData.blindfold)
+      return;
+
+    /* Figure out which square we start in and the
+       mouse position relative to top left corner. */
+    BoardSquare(x, y, &boardX, &boardY);
+    anims[Player].startBoardX = boardX;
+    anims[Player].startBoardY = boardY;
+    ScreenSquare(boardX, boardY, &corner, &color);
+    anims[Player].startSquare  = corner;
+    anims[Player].startColor   = color;
+    /* As soon as we start dragging, the piece will jump slightly to
+       be centered over the mouse pointer. */
+    anims[Player].mouseDelta.x = squareSize/2;
+    anims[Player].mouseDelta.y = squareSize/2;
+    /* Initialise animation */
+    anims[Player].dragPiece = PieceForSquare(boardX, boardY);
+    /* Sanity check */
+    if (anims[Player].dragPiece >= 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<appData.flashCount; ++i) {
+           DrawOneSquare(x, y, piece, square_color, 0, string, 0);
+           FlashDelay(flash_delay);
+           DrawOneSquare(x, y, EmptySquare, square_color, 0, string, 0);
+           FlashDelay(flash_delay);
+       }
+    }
+    DrawOneSquare(x, y, piece, square_color, partnerUp ? 0 : marker[row][column], string, align);
+}
+
+/* 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<BOARD_HEIGHT; ++i) {
+       for (j=0; j<BOARD_WIDTH; ++j) {
+           if (b1[i][j] != b2[i][j]) {
+               if (++c > 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 (file)
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];
+
index ec7568c..934f903 100644 (file)
--- 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<appData.flashCount; ++i) {
-                                       drawfunc(piece, square_color, x, y, xBoardWindow);
-                                       XSync(xDisplay, False);
-                                       do_flash_delay(flash_delay);
-
-                                       BlankSquare(x, y, square_color, piece, xBoardWindow, 1);
-                                       XSync(xDisplay, False);
-                                       do_flash_delay(flash_delay);
-                           }
-                       }
-                       drawfunc(piece, square_color, x, y, xBoardWindow);
-       }
-       }
-
-    string[1] = NULLCHAR;
-    if (appData.showCoords && row == (flipView ? BOARD_HEIGHT-1 : 0)
-               && column >= 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<BOARD_HEIGHT; ++i) {
-       for (j=0; j<BOARD_WIDTH; ++j) {
-           if (b1[i][j] != b2[i][j]) {
-               if (++c > 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<BOARD_WIDTH; i++) for(j=0; j<BOARD_HEIGHT; j++)
-      if((i-toX)*(i-toX) + (j-toY)*(j-toY) < 6) damage[0][j][i] = True;
-  }
-
-  /* Be sure end square is redrawn */
-  damage[0][toY][toX] = True;
+    BlankSquare(x, y, startColor, EmptySquare, animBufs[anr+2], 0);
 }
 
-void
-DragPieceBegin (int x, int y, Boolean instantly)
+void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
+                int srcX, int srcY, int width, int height, int destX, int destY)
 {
-    int         boardX, boardY, color;
-    XPoint corner;
-
-    /* Are we animating? */
-    if (!appData.animateDragging || appData.blindfold)
-      return;
-
-    /* Figure out which square we start in and the
-       mouse position relative to top left corner. */
-    BoardSquare(x, y, &boardX, &boardY);
-    player.startBoardX = boardX;
-    player.startBoardY = boardY;
-    ScreenSquare(boardX, boardY, &corner, &color);
-    player.startSquare  = corner;
-    player.startColor   = color;
-    /* As soon as we start dragging, the piece will jump slightly to
-       be centered over the mouse pointer. */
-    player.mouseDelta.x = squareSize/2;
-    player.mouseDelta.y = squareSize/2;
-    /* Initialise animation */
-    player.dragPiece = PieceForSquare(boardX, boardY);
-    /* Sanity check */
-    if (player.dragPiece >= 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 <sys/ioctl.h>
@@ -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