Streamline XBoard board drawing
[xboard.git] / board.c
1 /*
2  * board.c -- platform-independent drawing code for XBoard
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9  * Software Foundation, Inc.
10  *
11  * The following terms apply to Digital Equipment Corporation's copyright
12  * interest in XBoard:
13  * ------------------------------------------------------------------------
14  * All Rights Reserved
15  *
16  * Permission to use, copy, modify, and distribute this software and its
17  * documentation for any purpose and without fee is hereby granted,
18  * provided that the above copyright notice appear in all copies and that
19  * both that copyright notice and this permission notice appear in
20  * supporting documentation, and that the name of Digital not be
21  * used in advertising or publicity pertaining to distribution of the
22  * software without specific, written prior permission.
23  *
24  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
25  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
26  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
27  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
28  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
29  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
30  * SOFTWARE.
31  * ------------------------------------------------------------------------
32  *
33  * The following terms apply to the enhanced version of XBoard
34  * distributed by the Free Software Foundation:
35  * ------------------------------------------------------------------------
36  *
37  * GNU XBoard is free software: you can redistribute it and/or modify
38  * it under the terms of the GNU General Public License as published by
39  * the Free Software Foundation, either version 3 of the License, or (at
40  * your option) any later version.
41  *
42  * GNU XBoard is distributed in the hope that it will be useful, but
43  * WITHOUT ANY WARRANTY; without even the implied warranty of
44  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
45  * General Public License for more details.
46  *
47  * You should have received a copy of the GNU General Public License
48  * along with this program. If not, see http://www.gnu.org/licenses/.  *
49  *
50  *------------------------------------------------------------------------
51  ** See the file ChangeLog for a revision history.  */
52
53 #define HIGHDRAG 1
54
55 #include "config.h"
56
57 #include <stdio.h>
58 #include <ctype.h>
59 #include <signal.h>
60 #include <errno.h>
61 #include <sys/types.h>
62 #include <sys/stat.h>
63 #include <pwd.h>
64 #include <math.h>
65
66 #if STDC_HEADERS
67 # include <stdlib.h>
68 # include <string.h>
69 #else /* not STDC_HEADERS */
70 extern char *getenv();
71 # if HAVE_STRING_H
72 #  include <string.h>
73 # else /* not HAVE_STRING_H */
74 #  include <strings.h>
75 # endif /* not HAVE_STRING_H */
76 #endif /* not STDC_HEADERS */
77
78 #if TIME_WITH_SYS_TIME
79 # include <sys/time.h>
80 # include <time.h>
81 #else
82 # if HAVE_SYS_TIME_H
83 #  include <sys/time.h>
84 # else
85 #  include <time.h>
86 # endif
87 #endif
88
89 #if HAVE_UNISTD_H
90 # include <unistd.h>
91 #endif
92
93 #if HAVE_SYS_WAIT_H
94 # include <sys/wait.h>
95 #endif
96
97 #include "common.h"
98 #include "frontend.h"
99 #include "backend.h"
100 #include "xboard2.h"
101 #include "moves.h"
102 #include "board.h"
103 #include "draw.h"
104
105
106 #ifdef __EMX__
107 #ifndef HAVE_USLEEP
108 #define HAVE_USLEEP
109 #endif
110 #define usleep(t)   _sleep2(((t)+500)/1000)
111 #endif
112
113
114 int squareSize, lineGap;
115
116 int damage[2][BOARD_RANKS][BOARD_FILES];
117
118 /* There can be two pieces being animated at once: a player
119    can begin dragging a piece before the remote opponent has moved. */
120
121 AnimState anims[NrOfAnims];
122
123 static void DrawSquare P((int row, int column, ChessSquare piece, int do_flash));
124 static Boolean IsDrawArrowEnabled P((void));
125 static void DrawArrowHighlight P((int fromX, int fromY, int toX,int toY));
126 static void ArrowDamage P((int s_col, int s_row, int d_col, int d_row));
127
128 static void
129 drawHighlight (int file, int rank, int type)
130 {
131     int x, y;
132
133     if (lineGap == 0) return;
134
135     if (flipView) {
136         x = lineGap/2 + ((BOARD_WIDTH-1)-file) *
137           (squareSize + lineGap);
138         y = lineGap/2 + rank * (squareSize + lineGap);
139     } else {
140         x = lineGap/2 + file * (squareSize + lineGap);
141         y = lineGap/2 + ((BOARD_HEIGHT-1)-rank) *
142           (squareSize + lineGap);
143     }
144
145     DrawBorder(x,y, type, lineGap & 1); // pass whether lineGap is odd
146 }
147
148 int hi1X = -1, hi1Y = -1, hi2X = -1, hi2Y = -1;
149 int pm1X = -1, pm1Y = -1, pm2X = -1, pm2Y = -1;
150
151 void
152 SetHighlights (int fromX, int fromY, int toX, int toY)
153 {   // [HGM] schedule old for erasure, and leave drawing new to DrawPosition
154     int change = 0;
155
156     if (hi1X >= 0 && hi1Y >= 0) {
157         if (hi1X != fromX || hi1Y != fromY) {
158             damage[0][hi1Y][hi1X] |= 2;
159             change |= 1;
160         }
161         change |= 4;
162     }
163
164     if (hi2X >= 0 && hi2Y >= 0) {
165         if (hi2X != toX || hi2Y != toY) {
166             damage[0][hi2Y][hi2X] |= 2;
167             change |= 2;
168         }
169         change |= 8;
170     }
171
172     if(change > 12 && IsDrawArrowEnabled()) ArrowDamage(hi1X, hi1Y, hi2X, hi2Y);
173
174     hi1X = fromX;
175     hi1Y = fromY;
176     hi2X = toX;
177     hi2Y = toY;
178 }
179
180 void
181 ClearHighlights ()
182 {
183     SetHighlights(-1, -1, -1, -1);
184 }
185
186
187 void
188 SetPremoveHighlights (int fromX, int fromY, int toX, int toY)
189 {
190     if (pm1X != fromX || pm1Y != fromY) {
191         if (pm1X >= 0 && pm1Y >= 0) {
192             damage[0][pm1Y][pm1X] |= 2;
193         }
194     }
195     if (pm2X != toX || pm2Y != toY) {
196         if (pm2X >= 0 && pm2Y >= 0) {
197             damage[0][pm1Y][pm1X] |= 2;
198         }
199     }
200     pm1X = fromX;
201     pm1Y = fromY;
202     pm2X = toX;
203     pm2Y = toY;
204 }
205
206 void
207 ClearPremoveHighlights ()
208 {
209   SetPremoveHighlights(-1, -1, -1, -1);
210 }
211
212 /*
213  * If the user selects on a border boundary, return -1; if off the board,
214  *   return -2.  Otherwise map the event coordinate to the square.
215  */
216 int
217 EventToSquare (int x, int limit)
218 {
219     if (x <= 0)
220       return -2;
221     if (x < lineGap)
222       return -1;
223     x -= lineGap;
224     if ((x % (squareSize + lineGap)) >= squareSize)
225       return -1;
226     x /= (squareSize + lineGap);
227     if (x >= limit)
228       return -2;
229     return x;
230 }
231
232 /* [HR] determine square color depending on chess variant. */
233 int
234 SquareColor (int row, int column)
235 {
236     int square_color;
237
238     if (gameInfo.variant == VariantXiangqi) {
239         if (column >= 3 && column <= 5 && row >= 0 && row <= 2) {
240             square_color = 1;
241         } else if (column >= 3 && column <= 5 && row >= 7 && row <= 9) {
242             square_color = 0;
243         } else if (row <= 4) {
244             square_color = 0;
245         } else {
246             square_color = 1;
247         }
248     } else {
249         square_color = ((column + row) % 2) == 1;
250     }
251
252     /* [hgm] holdings: next line makes all holdings squares light */
253     if(column < BOARD_LEFT || column >= BOARD_RGHT) square_color = 1;
254
255     if ( // [HGM] holdings: blank out area between board and holdings
256                  column == BOARD_LEFT-1
257              ||  column == BOARD_RGHT
258              || (column == BOARD_LEFT-2 && row < BOARD_HEIGHT-gameInfo.holdingsSize)
259              || (column == BOARD_RGHT+1 && row >= gameInfo.holdingsSize) )
260         square_color = 2; // black
261
262     return square_color;
263 }
264
265 /*      Convert board position to corner of screen rect and color       */
266
267 void
268 ScreenSquare (int column, int row, Pnt *pt, int *color)
269 {
270   if (flipView) {
271     pt->x = lineGap + ((BOARD_WIDTH-1)-column) * (squareSize + lineGap);
272     pt->y = lineGap + row * (squareSize + lineGap);
273   } else {
274     pt->x = lineGap + column * (squareSize + lineGap);
275     pt->y = lineGap + ((BOARD_HEIGHT-1)-row) * (squareSize + lineGap);
276   }
277   *color = SquareColor(row, column);
278 }
279
280 /*      Convert window coords to square                 */
281
282 void
283 BoardSquare (int x, int y, int *column, int *row)
284 {
285   *column = EventToSquare(x, BOARD_WIDTH);
286   if (flipView && *column >= 0)
287     *column = BOARD_WIDTH - 1 - *column;
288   *row = EventToSquare(y, BOARD_HEIGHT);
289   if (!flipView && *row >= 0)
290     *row = BOARD_HEIGHT - 1 - *row;
291 }
292
293 /*      Generate a series of frame coords from start->mid->finish.
294         The movement rate doubles until the half way point is
295         reached, then halves back down to the final destination,
296         which gives a nice slow in/out effect. The algorithmn
297         may seem to generate too many intermediates for short
298         moves, but remember that the purpose is to attract the
299         viewers attention to the piece about to be moved and
300         then to where it ends up. Too few frames would be less
301         noticeable.                                             */
302
303 static void
304 Tween (Pnt *start, Pnt *mid, Pnt *finish, int factor, Pnt frames[], int *nFrames)
305 {
306   int fraction, n, count;
307
308   count = 0;
309
310   /* Slow in, stepping 1/16th, then 1/8th, ... */
311   fraction = 1;
312   for (n = 0; n < factor; n++)
313     fraction *= 2;
314   for (n = 0; n < factor; n++) {
315     frames[count].x = start->x + (mid->x - start->x) / fraction;
316     frames[count].y = start->y + (mid->y - start->y) / fraction;
317     count ++;
318     fraction = fraction / 2;
319   }
320
321   /* Midpoint */
322   frames[count] = *mid;
323   count ++;
324
325   /* Slow out, stepping 1/2, then 1/4, ... */
326   fraction = 2;
327   for (n = 0; n < factor; n++) {
328     frames[count].x = finish->x - (finish->x - mid->x) / fraction;
329     frames[count].y = finish->y - (finish->y - mid->y) / fraction;
330     count ++;
331     fraction = fraction * 2;
332   }
333   *nFrames = count;
334 }
335
336 /****   Animation code by Hugh Fisher, DCS, ANU.
337
338         Known problem: if a window overlapping the board is
339         moved away while a piece is being animated underneath,
340         the newly exposed area won't be updated properly.
341         I can live with this.
342
343         Known problem: if you look carefully at the animation
344         of pieces in mono mode, they are being drawn as solid
345         shapes without interior detail while moving. Fixing
346         this would be a major complication for minimal return.
347 ****/
348
349 /*   Utilities  */
350
351 #undef Max  /* just in case */
352 #undef Min
353 #define Max(a, b) ((a) > (b) ? (a) : (b))
354 #define Min(a, b) ((a) < (b) ? (a) : (b))
355
356 typedef struct {
357   short int x, y, width, height;
358 } MyRectangle;
359
360 void
361 DoSleep (int n)
362 {
363     FrameDelay(n);
364 }
365
366 static void
367 SetRect (MyRectangle *rect, int x, int y, int width, int height)
368 {
369   rect->x = x;
370   rect->y = y;
371   rect->width  = width;
372   rect->height = height;
373 }
374
375 /*      Test if two frames overlap. If they do, return
376         intersection rect within old and location of
377         that rect within new. */
378
379 static Boolean
380 Intersect ( Pnt *old, Pnt *new, int size, MyRectangle *area, Pnt *pt)
381 {
382   if (old->x > new->x + size || new->x > old->x + size ||
383       old->y > new->y + size || new->y > old->y + size) {
384     return False;
385   } else {
386     SetRect(area, Max(new->x - old->x, 0), Max(new->y - old->y, 0),
387             size - abs(old->x - new->x), size - abs(old->y - new->y));
388     pt->x = Max(old->x - new->x, 0);
389     pt->y = Max(old->y - new->y, 0);
390     return True;
391   }
392 }
393
394 /*      For two overlapping frames, return the rect(s)
395         in the old that do not intersect with the new.   */
396
397 static void
398 CalcUpdateRects (Pnt *old, Pnt *new, int size, MyRectangle update[], int *nUpdates)
399 {
400   int        count;
401
402   /* If old = new (shouldn't happen) then nothing to draw */
403   if (old->x == new->x && old->y == new->y) {
404     *nUpdates = 0;
405     return;
406   }
407   /* Work out what bits overlap. Since we know the rects
408      are the same size we don't need a full intersect calc. */
409   count = 0;
410   /* Top or bottom edge? */
411   if (new->y > old->y) {
412     SetRect(&(update[count]), old->x, old->y, size, new->y - old->y);
413     count ++;
414   } else if (old->y > new->y) {
415     SetRect(&(update[count]), old->x, old->y + size - (old->y - new->y),
416                               size, old->y - new->y);
417     count ++;
418   }
419   /* Left or right edge - don't overlap any update calculated above. */
420   if (new->x > old->x) {
421     SetRect(&(update[count]), old->x, Max(new->y, old->y),
422                               new->x - old->x, size - abs(new->y - old->y));
423     count ++;
424   } else if (old->x > new->x) {
425     SetRect(&(update[count]), new->x + size, Max(new->y, old->y),
426                               old->x - new->x, size - abs(new->y - old->y));
427     count ++;
428   }
429   /* Done */
430   *nUpdates = count;
431 }
432
433 /* Animate the movement of a single piece */
434
435 static void
436 BeginAnimation (AnimNr anr, ChessSquare piece, ChessSquare bgPiece, int startColor, Pnt *start)
437 {
438   AnimState *anim = &anims[anr];
439
440   if(appData.upsideDown && flipView) piece += piece < BlackPawn ? BlackPawn : -BlackPawn;
441   /* The old buffer is initialised with the start square (empty) */
442   if(bgPiece == EmptySquare) {
443     DrawBlank(anr, start->x, start->y, startColor);
444   } else {
445        /* Kludge alert: When gating we want the introduced
446           piece to appear on the from square. To generate an
447           image of it, we draw it on the board, copy the image,
448           and draw the original piece again. */
449        if(piece != bgPiece) DrawSquare(anim->startBoardY, anim->startBoardX, bgPiece, 0);
450        CopyRectangle(anr, DISP, 2,
451                  start->x, start->y, squareSize, squareSize,
452                  0, 0); // [HGM] zh: unstack in stead of grab
453        if(piece != bgPiece) DrawSquare(anim->startBoardY, anim->startBoardX, piece, 0);
454   }
455   anim->prevFrame = *start;
456
457   SetDragPiece(anr, piece);
458 }
459
460 static void
461 AnimationFrame (AnimNr anr, Pnt *frame, ChessSquare piece)
462 {
463   MyRectangle updates[4];
464   MyRectangle overlap;
465   Pnt     pt;
466   AnimState *anim = &anims[anr];
467   int     count, i, x, y, w, h;
468
469   /* Save what we are about to draw into the new buffer */
470   CopyRectangle(anr, DISP, 0,
471             x = frame->x, y = frame->y, w = squareSize, h = squareSize,
472             0, 0);
473
474   /* Erase bits of the previous frame */
475   if (Intersect(&anim->prevFrame, frame, squareSize, &overlap, &pt)) {
476     /* Where the new frame overlapped the previous,
477        the contents in newBuf are wrong. */
478     CopyRectangle(anr, 2, 0,
479               overlap.x, overlap.y,
480               overlap.width, overlap.height,
481               pt.x, pt.y);
482     /* Repaint the areas in the old that don't overlap new */
483     CalcUpdateRects(&anim->prevFrame, frame, squareSize, updates, &count);
484     for (i = 0; i < count; i++)
485       CopyRectangle(anr, 2, DISP,
486                 updates[i].x - anim->prevFrame.x,
487                 updates[i].y - anim->prevFrame.y,
488                 updates[i].width, updates[i].height,
489                 updates[i].x, updates[i].y);
490     /* [HGM] correct expose rectangle to encompass both overlapping squares */
491     if(x > anim->prevFrame.x) w += x - anim->prevFrame.x, x = anim->prevFrame.x;
492     else  w += anim->prevFrame.x - x;
493     if(y > anim->prevFrame.y) h += y - anim->prevFrame.y, y = anim->prevFrame.y;
494     else  h += anim->prevFrame.y - y;
495   } else {
496     /* Easy when no overlap */
497     CopyRectangle(anr, 2, DISP,
498                   0, 0, squareSize, squareSize,
499                   anim->prevFrame.x, anim->prevFrame.y);
500     GraphExpose(currBoard, anim->prevFrame.x, anim->prevFrame.y, squareSize, squareSize);
501   }
502
503   /* Save this frame for next time round */
504   CopyRectangle(anr, 0, 2,
505                 0, 0, squareSize, squareSize,
506                 0, 0);
507   anim->prevFrame = *frame;
508
509   /* Draw piece over original screen contents, not current,
510      and copy entire rect. Wipes out overlapping piece images. */
511   InsertPiece(anr, piece);
512   CopyRectangle(anr, 0, DISP,
513                 0, 0, squareSize, squareSize,
514                 frame->x, frame->y);
515   GraphExpose(currBoard, x, y, w, h);
516 }
517
518 static void
519 EndAnimation (AnimNr anr, Pnt *finish)
520 {
521   MyRectangle updates[4];
522   MyRectangle overlap;
523   Pnt     pt;
524   int        count, i;
525   AnimState *anim = &anims[anr];
526
527   /* The main code will redraw the final square, so we
528      only need to erase the bits that don't overlap.    */
529   if (Intersect(&anim->prevFrame, finish, squareSize, &overlap, &pt)) {
530     CalcUpdateRects(&anim->prevFrame, finish, squareSize, updates, &count);
531     for (i = 0; i < count; i++)
532       CopyRectangle(anr, 2, DISP,
533                 updates[i].x - anim->prevFrame.x,
534                 updates[i].y - anim->prevFrame.y,
535                 updates[i].width, updates[i].height,
536                 updates[i].x, updates[i].y);
537   } else {
538     CopyRectangle(anr, 2, DISP,
539                 0, 0, squareSize, squareSize,
540                 anim->prevFrame.x, anim->prevFrame.y);
541   }
542 }
543
544 static void
545 FrameSequence (AnimNr anr, ChessSquare piece, int startColor, Pnt *start, Pnt *finish, Pnt frames[], int nFrames)
546 {
547   int n;
548
549   BeginAnimation(anr, piece, EmptySquare, startColor, start);
550   for (n = 0; n < nFrames; n++) {
551     AnimationFrame(anr, &(frames[n]), piece);
552     FrameDelay(appData.animSpeed);
553   }
554   EndAnimation(anr, finish);
555 }
556
557 void
558 AnimateAtomicCapture (Board board, int fromX, int fromY, int toX, int toY)
559 {
560     int i, x, y;
561     ChessSquare piece = board[fromY][toY];
562     board[fromY][toY] = EmptySquare;
563     DrawPosition(FALSE, board);
564     if (flipView) {
565         x = lineGap + ((BOARD_WIDTH-1)-toX) * (squareSize + lineGap);
566         y = lineGap + toY * (squareSize + lineGap);
567     } else {
568         x = lineGap + toX * (squareSize + lineGap);
569         y = lineGap + ((BOARD_HEIGHT-1)-toY) * (squareSize + lineGap);
570     }
571     for(i=1; i<4*kFactor; i++) {
572         int r = squareSize * 9 * i/(20*kFactor - 5);
573         DrawDot(1, x + squareSize/2 - r, y+squareSize/2 - r, 2*r);
574         FrameDelay(appData.animSpeed);
575     }
576     board[fromY][toY] = piece;
577     DrawGrid();
578 }
579
580 /* Main control logic for deciding what to animate and how */
581
582 void
583 AnimateMove (Board board, int fromX, int fromY, int toX, int toY)
584 {
585   ChessSquare piece;
586   int hop, x = toX, y = toY, x2 = kill2X;
587   Pnt      start, finish, mid;
588   Pnt      frames[kFactor * 2 + 1];
589   int         nFrames, startColor, endColor;
590
591   if(killX >= 0 && IS_LION(board[fromY][fromX])) Roar();
592
593   /* Are we animating? */
594   if (!appData.animate || appData.blindfold)
595     return;
596
597   if(board[toY][toX] == WhiteRook && board[fromY][fromX] == WhiteKing ||
598      board[toY][toX] == BlackRook && board[fromY][fromX] == BlackKing ||
599      board[toY][toX] == WhiteKing && board[fromY][fromX] == WhiteRook || // [HGM] seirawan
600      board[toY][toX] == BlackKing && board[fromY][fromX] == BlackRook)
601         return; // [HGM] FRC: no animtion of FRC castlings, as to-square is not true to-square
602
603   if (fromY < 0 || fromX < 0 || toX < 0 || toY < 0) return;
604   piece = board[fromY][fromX];
605   if (piece >= EmptySquare) return;
606
607   if(x2 >= 0) toX = kill2X, toY = kill2Y; else
608   if(killX >= 0) toX = killX, toY = killY; // [HGM] lion: first to kill square
609
610 again:
611
612 #if DONT_HOP
613   hop = FALSE;
614 #else
615   hop = abs(fromX-toX) == 1 && abs(fromY-toY) == 2 || abs(fromX-toX) == 2 && abs(fromY-toY) == 1;
616 #endif
617
618   ScreenSquare(fromX, fromY, &start, &startColor);
619   ScreenSquare(toX, toY, &finish, &endColor);
620
621   if (hop) {
622     /* Knight: make straight movement then diagonal */
623     if (abs(toY - fromY) < abs(toX - fromX)) {
624        mid.x = start.x + (finish.x - start.x) / 2;
625        mid.y = start.y;
626      } else {
627        mid.x = start.x;
628        mid.y = start.y + (finish.y - start.y) / 2;
629      }
630   } else {
631     mid.x = start.x + (finish.x - start.x) / 2;
632     mid.y = start.y + (finish.y - start.y) / 2;
633   }
634
635   /* Don't use as many frames for very short moves */
636   if (abs(toY - fromY) + abs(toX - fromX) <= 2)
637     Tween(&start, &mid, &finish, kFactor - 1, frames, &nFrames);
638   else
639     Tween(&start, &mid, &finish, kFactor, frames, &nFrames);
640   FrameSequence(Game, piece, startColor, &start, &finish, frames, nFrames);
641   if(Explode(board, fromX, fromY, toX, toY)) { // mark as damaged
642     int i,j;
643     for(i=0; i<BOARD_WIDTH; i++) for(j=0; j<BOARD_HEIGHT; j++)
644       if((i-toX)*(i-toX) + (j-toY)*(j-toY) < 6) damage[0][j][i] |=  1 + ((i-toX ^ j-toY) & 1);
645   }
646
647   /* Be sure end square is redrawn, with piece in it */
648   damage[0][toY][toX] |= 4;
649
650   if(toX == x2 && toY == kill2Y) { fromX = toX; fromY = toY; toX = killX; toY = killY; x2 = -1; goto again; } // second leg
651   if(toX != x || toY != y) { fromX = toX; fromY = toY; toX = x; toY = y; goto again; } // second leg
652 }
653
654 void
655 ChangeDragPiece (ChessSquare piece)
656 {
657   anims[Player].dragPiece = piece;
658   SetDragPiece(Player, piece);
659   damage[0][fromY][fromX] = True;
660 }
661
662 void
663 DragPieceMove (int x, int y)
664 {
665     Pnt corner;
666
667     /* Are we animating? */
668     if (!appData.animateDragging || appData.blindfold)
669       return;
670
671     /* Sanity check */
672     if (! anims[Player].dragActive)
673       return;
674     /* Move piece, maintaining same relative position
675        of mouse within square    */
676     corner.x = x - anims[Player].mouseDelta.x;
677     corner.y = y - anims[Player].mouseDelta.y;
678     AnimationFrame(Player, &corner, anims[Player].dragPiece);
679 #if HIGHDRAG*0
680     if (appData.highlightDragging) {
681         int boardX, boardY;
682         BoardSquare(x, y, &boardX, &boardY);
683         SetHighlights(fromX, fromY, boardX, boardY);
684     }
685 #endif
686 }
687
688 void
689 DragPieceEnd (int x, int y)
690 {
691     int boardX, boardY, color;
692     Pnt corner;
693
694     /* Are we animating? */
695     if (!appData.animateDragging || appData.blindfold)
696       return;
697
698     /* Sanity check */
699     if (! anims[Player].dragActive)
700       return;
701     /* Last frame in sequence is square piece is
702        placed on, which may not match mouse exactly. */
703     BoardSquare(x, y, &boardX, &boardY);
704     ScreenSquare(boardX, boardY, &corner, &color);
705     EndAnimation(Player, &corner);
706
707     /* Be sure end square is redrawn */
708     damage[0][boardY][boardX] = True;
709
710     /* This prevents weird things happening with fast successive
711        clicks which on my Sun at least can cause motion events
712        without corresponding press/release. */
713     anims[Player].dragActive = False;
714 }
715
716 void
717 DragPieceBegin (int x, int y, Boolean instantly)
718 {
719     int  boardX, boardY, color;
720     Pnt corner;
721
722     /* Are we animating? */
723     if (!appData.animateDragging || appData.blindfold)
724       return;
725
726     /* Figure out which square we start in and the
727        mouse position relative to top left corner. */
728     BoardSquare(x, y, &boardX, &boardY);
729     anims[Player].startBoardX = boardX;
730     anims[Player].startBoardY = boardY;
731     ScreenSquare(boardX, boardY, &corner, &color);
732     anims[Player].startSquare  = corner;
733     anims[Player].startColor   = color;
734     /* As soon as we start dragging, the piece will jump slightly to
735        be centered over the mouse pointer. */
736     anims[Player].mouseDelta.x = squareSize/2;
737     anims[Player].mouseDelta.y = squareSize/2;
738     /* Initialise animation */
739     anims[Player].dragPiece = PieceForSquare(boardX, boardY);
740     /* Sanity check */
741     if (anims[Player].dragPiece >= 0 && anims[Player].dragPiece < EmptySquare) {
742         ChessSquare bgPiece = EmptySquare;
743         anims[Player].dragActive = True;
744         if(boardX == BOARD_RGHT+1 && PieceForSquare(boardX-1, boardY) > 1 ||
745            boardX == BOARD_LEFT-2 && PieceForSquare(boardX+1, boardY) > 1)
746             bgPiece = anims[Player].dragPiece;
747         if(gatingPiece != EmptySquare) bgPiece = gatingPiece;
748         BeginAnimation(Player, anims[Player].dragPiece, bgPiece, color, &corner);
749         /* Mark this square as needing to be redrawn. Note that
750            we don't remove the piece though, since logically (ie
751            as seen by opponent) the move hasn't been made yet. */
752         damage[0][boardY][boardX] = True;
753     } else {
754         anims[Player].dragActive = False;
755     }
756 }
757
758 /* Handle expose event while piece being dragged */
759
760 static void
761 DrawDragPiece ()
762 {
763   if (!anims[Player].dragActive || appData.blindfold)
764     return;
765
766   /* What we're doing: logically, the move hasn't been made yet,
767      so the piece is still in it's original square. But visually
768      it's being dragged around the board. So we erase the square
769      that the piece is on and draw it at the last known drag point. */
770   DrawOneSquare(anims[Player].startSquare.x, anims[Player].startSquare.y,
771                 EmptySquare, anims[Player].startColor, 0, NULL, NULL, 0);
772   AnimationFrame(Player, &anims[Player].prevFrame, anims[Player].dragPiece);
773   damage[0][anims[Player].startBoardY][anims[Player].startBoardX] = TRUE;
774 }
775
776 static void
777 DrawSquare (int row, int column, ChessSquare piece, int do_flash)
778 {
779     int square_color, x, y, align=0;
780     int i;
781     char tString[3], bString[2];
782     int flash_delay;
783
784     /* Calculate delay in milliseconds (2-delays per complete flash) */
785     flash_delay = 500 / appData.flashRate;
786
787     if (flipView) {
788         x = lineGap + ((BOARD_WIDTH-1)-column) *
789           (squareSize + lineGap);
790         y = lineGap + row * (squareSize + lineGap);
791     } else {
792         x = lineGap + column * (squareSize + lineGap);
793         y = lineGap + ((BOARD_HEIGHT-1)-row) *
794           (squareSize + lineGap);
795     }
796
797     square_color = SquareColor(row, column);
798
799     bString[1] = bString[0] = NULLCHAR;
800     if (appData.showCoords && row == (flipView ? BOARD_HEIGHT-1 : 0)
801                 && column >= BOARD_LEFT && column < BOARD_RGHT) {
802         bString[0] = 'a' + column - BOARD_LEFT;
803         align = 1; // coord in lower-right corner
804     }
805     if (appData.showCoords && column == (flipView ? BOARD_RGHT-1 : BOARD_LEFT)) {
806         snprintf(tString, 3, "%d", ONE - '0' + row);
807         align = 2; // coord in upper-left corner
808     }
809     if (column == (flipView ? BOARD_LEFT-1 : BOARD_RGHT) && piece > 1 ) {
810         snprintf(tString, 3, "%d", piece);
811         align = 3; // holdings count in upper-right corner
812     }
813     if (column == (flipView ? BOARD_RGHT : BOARD_LEFT-1) && piece > 1) {
814         snprintf(tString, 3, "%d", piece);
815         align = 4; // holdings count in upper-left corner
816     }
817     if(piece == DarkSquare) square_color = 2;
818     if(square_color == 2 || appData.blindfold) piece = EmptySquare;
819
820     if (do_flash && piece != EmptySquare && appData.flashCount > 0) {
821         for (i=0; i<appData.flashCount; ++i) {
822             DrawOneSquare(x, y, piece, square_color, 0, tString, bString, 0);
823             GraphExpose(currBoard, x, y, squareSize, squareSize);
824             FlashDelay(flash_delay);
825             DrawOneSquare(x, y, EmptySquare, square_color, 0, tString, bString, 0);
826             GraphExpose(currBoard, x, y, squareSize, squareSize);
827             FlashDelay(flash_delay);
828         }
829     }
830     DrawOneSquare(x, y, piece, square_color, partnerUp ? 0 : marker[row][column], tString, bString, align);
831 }
832
833 /* Returns 1 if there are "too many" differences between b1 and b2
834    (i.e. more than 1 move was made) */
835 static int
836 too_many_diffs (Board b1, Board b2)
837 {
838     int i, j;
839     int c = 0;
840
841     for (i=0; i<BOARD_HEIGHT; ++i) {
842         for (j=0; j<BOARD_WIDTH; ++j) {
843             if (b1[i][j] != b2[i][j]) {
844                 if (++c > 4)    /* Castling causes 4 diffs */
845                   return 1;
846             }
847         }
848     }
849     return 0;
850 }
851
852 /* Matrix describing castling maneuvers */
853 /* Row, ColRookFrom, ColKingFrom, ColRookTo, ColKingTo */
854 static int castling_matrix[4][5] = {
855     { 0, 0, 4, 3, 2 },          /* 0-0-0, white */
856     { 0, 7, 4, 5, 6 },          /* 0-0,   white */
857     { 7, 0, 4, 3, 2 },          /* 0-0-0, black */
858     { 7, 7, 4, 5, 6 }           /* 0-0,   black */
859 };
860
861 /* Checks whether castling occurred. If it did, *rrow and *rcol
862    are set to the destination (row,col) of the rook that moved.
863
864    Returns 1 if castling occurred, 0 if not.
865
866    Note: Only handles a max of 1 castling move, so be sure
867    to call too_many_diffs() first.
868    */
869 static int
870 check_castle_draw (Board newb, Board oldb, int *rrow, int *rcol)
871 {
872     int i, *r, j;
873     int match;
874
875     /* For each type of castling... */
876     for (i=0; i<4; ++i) {
877         r = castling_matrix[i];
878
879         /* Check the 4 squares involved in the castling move */
880         match = 0;
881         for (j=1; j<=4; ++j) {
882             if (newb[r[0]][r[j]] == oldb[r[0]][r[j]]) {
883                 match = 1;
884                 break;
885             }
886         }
887
888         if (!match) {
889             /* All 4 changed, so it must be a castling move */
890             *rrow = r[0];
891             *rcol = r[3];
892             return 1;
893         }
894     }
895     return 0;
896 }
897
898 void
899 SquareExpose(int i, int j, int d)
900 {
901     int x, y;
902     if (flipView) {
903         x = lineGap + ((BOARD_WIDTH-1)-j) *
904           (squareSize + lineGap);
905         y = lineGap + i * (squareSize + lineGap);
906     } else {
907         x = lineGap + j * (squareSize + lineGap);
908         y = lineGap + ((BOARD_HEIGHT-1)-i) *
909           (squareSize + lineGap);
910     }
911     GraphExpose(currBoard, x-d, y-d, squareSize+2*d, squareSize+2*d);
912 }
913
914 void
915 DrawPosition (int repaint, Board board)
916 {
917     int i, j, do_flash, exposeAll = False;
918     static int lastFlipView = 0;
919     static int lastBoardValid[2] = {0, 0};
920     static Board lastBoard[2];
921     static char lastMarker[BOARD_RANKS][BOARD_FILES], messedUp;
922     int rrow = -1, rcol = -1;
923     int nr = twoBoards*partnerUp;
924
925     repaint |= messedUp;
926
927     if(DrawSeekGraph()) return; // [HGM] seekgraph: suppress any drawing if seek graph up
928
929     if (board == NULL) {
930         if (!lastBoardValid[nr]) return;
931         board = lastBoard[nr];
932     }
933     if (!lastBoardValid[nr] || (nr == 0 && lastFlipView != flipView)) {
934         MarkMenuItem("View.Flip View", flipView);
935     }
936
937     if(nr) { SlavePopUp(); SwitchWindow(0); } // [HGM] popup board if not yet popped up, and switch drawing to it.
938
939     /*
940      * It would be simpler to clear the window with XClearWindow()
941      * but this causes a very distracting flicker.
942      */
943
944     if (!repaint && lastBoardValid[nr] && (nr == 1 || lastFlipView == flipView)) {
945
946         /* If too much changes (begin observing new game, etc.), don't
947            do flashing */
948         do_flash = too_many_diffs(board, lastBoard[nr]) ? 0 : 1;
949
950         /* Special check for castling so we don't flash both the king
951            and the rook (just flash the king). */
952         if (do_flash) {
953             /* Mark rook for drawing with NO flashing. */
954             check_castle_draw(board, lastBoard[nr], &rrow, &rcol);
955         }
956
957         /* First pass -- Erase arrow and grid highlights, but keep square content unchanged. Except for new markers. */
958         for (i = 0; i < BOARD_HEIGHT; i++)
959           for (j = 0; j < BOARD_WIDTH; j++)
960             if (damage[nr][i][j] ||  !nr && marker[i][j] != lastMarker[i][j]) {
961                 DrawSquare(i, j, board[i][j], 0);
962                 if(lineGap && damage[nr][i][j] & 2) {
963                     drawHighlight(j, i, 0);
964                     SquareExpose(i, j, 1);
965                 } else SquareExpose(i, j, 0);
966                 damage[nr][i][j] = 0;
967             }
968
969         /* Second pass -- Draw (newly) empty squares
970            This prevents you from having a piece show up twice while it
971            is flashing on its new square */
972         for (i = 0; i < BOARD_HEIGHT; i++)
973           for (j = 0; j < BOARD_WIDTH; j++)
974             if (board[i][j] != lastBoard[nr][i][j] && board[i][j] == EmptySquare) {
975                 DrawSquare(i, j, board[i][j], 0);
976                 SquareExpose(i, j, 0);
977             }
978
979         /* Third pass -- Draw piece(s) in new position and flash them */
980         for (i = 0; i < BOARD_HEIGHT; i++)
981           for (j = 0; j < BOARD_WIDTH; j++)
982             if (board[i][j] != lastBoard[nr][i][j]) {
983                 DrawSquare(i, j, board[i][j], do_flash && (i != rrow || j != rcol));
984                 damage[nr][i][j] = 1; // mark for expose
985             }
986
987     } else {
988         if (lineGap > 0)
989           DrawGrid();
990
991         for (i = 0; i < BOARD_HEIGHT; i++)
992           for (j = 0; j < BOARD_WIDTH; j++) {
993               DrawSquare(i, j, board[i][j], 0);
994               damage[nr][i][j] = False;
995           }
996
997         exposeAll = True;
998     }
999
1000     CopyBoard(lastBoard[nr], board);
1001     lastBoardValid[nr] = 1;
1002   if(nr == 0) { // [HGM] dual: no highlights on second board yet
1003     lastFlipView = flipView;
1004     for (i = 0; i < BOARD_HEIGHT; i++)
1005         for (j = 0; j < BOARD_WIDTH; j++)
1006             lastMarker[i][j] = marker[i][j];
1007
1008     /* Draw highlights */
1009     if (pm1X >= 0 && pm1Y >= 0) {
1010       drawHighlight(pm1X, pm1Y, 2);
1011       if(lineGap) damage[nr][pm1Y][pm1X] |= 2;
1012     }
1013     if (pm2X >= 0 && pm2Y >= 0) {
1014       drawHighlight(pm2X, pm2Y, 2);
1015       if(lineGap) damage[nr][pm2Y][pm2X] |= 2;
1016     }
1017     if (hi1X >= 0 && hi1Y >= 0) {
1018       drawHighlight(hi1X, hi1Y, 1);
1019       if(lineGap) damage[nr][hi1Y][hi1X] |= 2;
1020     }
1021     if (hi2X >= 0 && hi2Y >= 0) {
1022       drawHighlight(hi2X, hi2Y, 1);
1023       if(lineGap) damage[nr][hi2Y][hi2X] |= 2;
1024     }
1025     DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y);
1026   }
1027   else DrawArrowHighlight (board[EP_STATUS-3], board[EP_STATUS-4], board[EP_STATUS-1], board[EP_STATUS-2]);
1028
1029     /* If piece being dragged around board, must redraw that too */
1030     DrawDragPiece();
1031
1032     if(exposeAll)
1033         GraphExpose(currBoard, 0, 0, BOARD_WIDTH*(squareSize + lineGap) + lineGap, BOARD_HEIGHT*(squareSize + lineGap) + lineGap);
1034     else {
1035         for (i = 0; i < BOARD_HEIGHT; i++)
1036             for (j = 0; j < BOARD_WIDTH; j++)
1037                 if(damage[nr][i][j]) {
1038                     if(damage[nr][i][j] & 2) // damage by old or new arrow
1039                         SquareExpose(i, j, lineGap);
1040                     else
1041                         SquareExpose(i, j, 0);
1042                     damage[nr][i][j] = 0;
1043                 }
1044     }
1045
1046     FlashDelay(0); // this flushes drawing queue;
1047     if(nr) SwitchWindow(1);
1048     else {
1049         TimeMark now;
1050         GetTimeMark(&now);
1051         if(SubtractTimeMarks(&now, &programStartTime) < 1000) {
1052             DrawSeekBackground(2*squareSize, 3*squareSize, 6*squareSize, 5*squareSize);
1053             DrawText("Right-clicking dialog texts", 2*squareSize + 5, 3*squareSize + 5, 2);
1054             DrawText("pops up help on them", 2*squareSize + 5, (int) (3.3*squareSize) + 5, 2);
1055             GraphExpose(currBoard, 2*squareSize, 3*squareSize, 4*squareSize, 2*squareSize);
1056             messedUp = TRUE;
1057         } else messedUp = FALSE;
1058     }
1059 }
1060
1061 /* [AS] Arrow highlighting support */
1062
1063 static double A_WIDTH = 5; /* Width of arrow body */
1064
1065 #define A_HEIGHT_FACTOR 6   /* Length of arrow "point", relative to body width */
1066 #define A_WIDTH_FACTOR  3   /* Width of arrow "point", relative to body width */
1067
1068 static double
1069 Sqr (double x)
1070 {
1071     return x*x;
1072 }
1073
1074 static int
1075 Round (double x)
1076 {
1077     return (int) (x + 0.5);
1078 }
1079
1080 void
1081 SquareToPos (int rank, int file, int *x, int *y)
1082 {
1083     if (flipView) {
1084         *x = lineGap + ((BOARD_WIDTH-1)-file) * (squareSize + lineGap);
1085         *y = lineGap + rank * (squareSize + lineGap);
1086     } else {
1087         *x = lineGap + file * (squareSize + lineGap);
1088         *y = lineGap + ((BOARD_HEIGHT-1)-rank) * (squareSize + lineGap);
1089     }
1090 }
1091
1092 /* Draw an arrow between two points using current settings */
1093 static void
1094 DrawArrowBetweenPoints (int s_x, int s_y, int d_x, int d_y)
1095 {
1096     Pnt arrow[8];
1097     double dx, dy, j, k, x, y;
1098
1099     if( d_x == s_x ) {
1100         int h = (d_y > s_y) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1101
1102         arrow[0].x = s_x + A_WIDTH + 0.5;
1103         arrow[0].y = s_y;
1104
1105         arrow[1].x = s_x + A_WIDTH + 0.5;
1106         arrow[1].y = d_y - h;
1107
1108         arrow[2].x = arrow[1].x + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1109         arrow[2].y = d_y - h;
1110
1111         arrow[3].x = d_x;
1112         arrow[3].y = d_y;
1113
1114         arrow[5].x = arrow[1].x - 2*A_WIDTH + 0.5;
1115         arrow[5].y = d_y - h;
1116
1117         arrow[4].x = arrow[5].x - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1118         arrow[4].y = d_y - h;
1119
1120         arrow[6].x = arrow[1].x - 2*A_WIDTH + 0.5;
1121         arrow[6].y = s_y;
1122     }
1123     else if( d_y == s_y ) {
1124         int w = (d_x > s_x) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1125
1126         arrow[0].x = s_x;
1127         arrow[0].y = s_y + A_WIDTH + 0.5;
1128
1129         arrow[1].x = d_x - w;
1130         arrow[1].y = s_y + A_WIDTH + 0.5;
1131
1132         arrow[2].x = d_x - w;
1133         arrow[2].y = arrow[1].y + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1134
1135         arrow[3].x = d_x;
1136         arrow[3].y = d_y;
1137
1138         arrow[5].x = d_x - w;
1139         arrow[5].y = arrow[1].y - 2*A_WIDTH + 0.5;
1140
1141         arrow[4].x = d_x - w;
1142         arrow[4].y = arrow[5].y - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1143
1144         arrow[6].x = s_x;
1145         arrow[6].y = arrow[1].y - 2*A_WIDTH + 0.5;
1146     }
1147     else {
1148         /* [AS] Needed a lot of paper for this! :-) */
1149         dy = (double) (d_y - s_y) / (double) (d_x - s_x);
1150         dx = (double) (s_x - d_x) / (double) (s_y - d_y);
1151
1152         j = sqrt( Sqr(A_WIDTH) / (1.0 + Sqr(dx)) );
1153
1154         k = sqrt( Sqr(A_WIDTH*A_HEIGHT_FACTOR) / (1.0 + Sqr(dy)) );
1155
1156         x = s_x;
1157         y = s_y;
1158
1159         arrow[0].x = Round(x - j);
1160         arrow[0].y = Round(y + j*dx);
1161
1162         arrow[1].x = Round(arrow[0].x + 2*j);   // [HGM] prevent width to be affected by rounding twice
1163         arrow[1].y = Round(arrow[0].y - 2*j*dx);
1164
1165         if( d_x > s_x ) {
1166             x = (double) d_x - k;
1167             y = (double) d_y - k*dy;
1168         }
1169         else {
1170             x = (double) d_x + k;
1171             y = (double) d_y + k*dy;
1172         }
1173
1174         x = Round(x); y = Round(y); // [HGM] make sure width of shaft is rounded the same way on both ends
1175
1176         arrow[6].x = Round(x - j);
1177         arrow[6].y = Round(y + j*dx);
1178
1179         arrow[2].x = Round(arrow[6].x + 2*j);
1180         arrow[2].y = Round(arrow[6].y - 2*j*dx);
1181
1182         arrow[3].x = Round(arrow[2].x + j*(A_WIDTH_FACTOR-1));
1183         arrow[3].y = Round(arrow[2].y - j*(A_WIDTH_FACTOR-1)*dx);
1184
1185         arrow[4].x = d_x;
1186         arrow[4].y = d_y;
1187
1188         arrow[5].x = Round(arrow[6].x - j*(A_WIDTH_FACTOR-1));
1189         arrow[5].y = Round(arrow[6].y + j*(A_WIDTH_FACTOR-1)*dx);
1190     }
1191
1192     DrawPolygon(arrow, 7);
1193 //    Polygon( hdc, arrow, 7 );
1194 }
1195
1196 static void
1197 ArrowDamage (int s_col, int s_row, int d_col, int d_row)
1198 {
1199     int hor, vert, i, n = partnerUp * twoBoards, delta = abs(d_row - s_row);
1200
1201     if( 2*(d_row - s_row) > abs(d_col - s_col) ) d_row = 4*d_row + 1; else 
1202     if( 2*(s_row - d_row) > abs(d_col - s_col) ) d_row = 4*d_row + 3; else d_row = 4*d_row + 2;
1203     if( 2*(d_col - s_col) > delta ) d_col = 4*d_col + 1; else 
1204     if( 2*(s_col - d_col) > delta ) d_col = 4*d_col + 3; else d_col = 4*d_col + 2;
1205     s_row = 4*s_row + 2; s_col = 4*s_col + 2;
1206
1207     hor = 64*s_col; vert = 64*s_row;
1208     for(i=0; i<= 64; i++) {
1209             damage[n][vert+30>>8][hor+30>>8] |= 2;
1210             damage[n][vert-30>>8][hor+30>>8] |= 2;
1211             damage[n][vert+30>>8][hor-30>>8] |= 2;
1212             damage[n][vert-30>>8][hor-30>>8] |= 2;
1213             hor += d_col - s_col; vert += d_row - s_row;
1214     }
1215 }
1216
1217 /* [AS] Draw an arrow between two squares */
1218 static void
1219 DrawArrowBetweenSquares (int s_col, int s_row, int d_col, int d_row)
1220 {
1221     int s_x, s_y, d_x, d_y, delta_y;
1222
1223     if( s_col == d_col && s_row == d_row ) {
1224         return;
1225     }
1226
1227     /* Get source and destination points */
1228     SquareToPos( s_row, s_col, &s_x, &s_y);
1229     SquareToPos( d_row, d_col, &d_x, &d_y);
1230     delta_y = abs(d_y - s_y);
1231
1232     if( d_y > s_y && 2*(d_y - s_y) > abs(d_x - s_x)) {
1233         d_y += squareSize / 2 - squareSize / 4; // [HGM] round towards same centers on all sides!
1234     }
1235     else if( d_y < s_y && 2*(s_y - d_y) > abs(d_x - s_x)) {
1236         d_y += squareSize / 2 + squareSize / 4;
1237     }
1238     else {
1239         d_y += squareSize / 2;
1240     }
1241
1242     if( d_x > s_x && 2*(d_x - s_x) > delta_y) {
1243         d_x += squareSize / 2 - squareSize / 4;
1244     }
1245     else if( d_x < s_x && 2*(s_x - d_x) > delta_y) {
1246         d_x += squareSize / 2 + squareSize / 4;
1247     }
1248     else {
1249         d_x += squareSize / 2;
1250     }
1251
1252     s_x += squareSize / 2;
1253     s_y += squareSize / 2;
1254
1255     /* Adjust width */
1256     A_WIDTH = squareSize / 14.; //[HGM] make float
1257
1258     DrawArrowBetweenPoints( s_x, s_y, d_x, d_y );
1259     ArrowDamage(s_col, s_row, d_col, d_row);
1260 }
1261
1262 static Boolean
1263 IsDrawArrowEnabled ()
1264 {
1265     return (appData.highlightMoveWithArrow || twoBoards && partnerUp) && squareSize >= 32;
1266 }
1267
1268 static void
1269 DrawArrowHighlight (int fromX, int fromY, int toX,int toY)
1270 {
1271     if( IsDrawArrowEnabled() && fromX >= 0 && fromY >= 0 && toX >= 0 && toY >= 0)
1272         DrawArrowBetweenSquares(fromX, fromY, toX, toY);
1273 }