Fix multi-leg promotions
[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][pm2Y][pm2X] |= 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             DoEvents(); // requires event processing to actually update screen :-(
825             FlashDelay(flash_delay);
826             DrawOneSquare(x, y, EmptySquare, square_color, 0, tString, bString, 0);
827             GraphExpose(currBoard, x, y, squareSize, squareSize);
828             DoEvents();
829             FlashDelay(flash_delay);
830         }
831     }
832     DrawOneSquare(x, y, piece, square_color, partnerUp ? 0 : marker[row][column], tString, bString, align);
833 }
834
835 /* Returns 1 if there are "too many" differences between b1 and b2
836    (i.e. more than 1 move was made) */
837 static int
838 too_many_diffs (Board b1, Board b2)
839 {
840     int i, j;
841     int c = 0;
842
843     for (i=0; i<BOARD_HEIGHT; ++i) {
844         for (j=0; j<BOARD_WIDTH; ++j) {
845             if (b1[i][j] != b2[i][j]) {
846                 if (++c > 4)    /* Castling causes 4 diffs */
847                   return 1;
848             }
849         }
850     }
851     return 0;
852 }
853
854 /* Matrix describing castling maneuvers */
855 /* Row, ColRookFrom, ColKingFrom, ColRookTo, ColKingTo */
856 static int castling_matrix[4][5] = {
857     { 0, 0, 4, 3, 2 },          /* 0-0-0, white */
858     { 0, 7, 4, 5, 6 },          /* 0-0,   white */
859     { 7, 0, 4, 3, 2 },          /* 0-0-0, black */
860     { 7, 7, 4, 5, 6 }           /* 0-0,   black */
861 };
862
863 /* Checks whether castling occurred. If it did, *rrow and *rcol
864    are set to the destination (row,col) of the rook that moved.
865
866    Returns 1 if castling occurred, 0 if not.
867
868    Note: Only handles a max of 1 castling move, so be sure
869    to call too_many_diffs() first.
870    */
871 static int
872 check_castle_draw (Board newb, Board oldb, int *rrow, int *rcol)
873 {
874     int i, *r, j;
875     int match;
876
877     /* For each type of castling... */
878     for (i=0; i<4; ++i) {
879         r = castling_matrix[i];
880
881         /* Check the 4 squares involved in the castling move */
882         match = 0;
883         for (j=1; j<=4; ++j) {
884             if (newb[r[0]][r[j]] == oldb[r[0]][r[j]]) {
885                 match = 1;
886                 break;
887             }
888         }
889
890         if (!match) {
891             /* All 4 changed, so it must be a castling move */
892             *rrow = r[0];
893             *rcol = r[3];
894             return 1;
895         }
896     }
897     return 0;
898 }
899
900 void
901 SquareExpose(int i, int j, int d)
902 {
903     int x, y;
904     if (flipView) {
905         x = lineGap + ((BOARD_WIDTH-1)-j) *
906           (squareSize + lineGap);
907         y = lineGap + i * (squareSize + lineGap);
908     } else {
909         x = lineGap + j * (squareSize + lineGap);
910         y = lineGap + ((BOARD_HEIGHT-1)-i) *
911           (squareSize + lineGap);
912     }
913     GraphExpose(currBoard, x-d, y-d, squareSize+2*d, squareSize+2*d);
914 }
915
916 void
917 DrawPosition (int repaint, Board board)
918 {
919     int i, j, do_flash, exposeAll = False;
920     static int lastFlipView = 0;
921     static int lastBoardValid[2] = {0, 0};
922     static Board lastBoard[2];
923     static char lastMarker[BOARD_RANKS][BOARD_FILES], messedUp;
924     int rrow = -1, rcol = -1;
925     int nr = twoBoards*partnerUp;
926
927     repaint |= messedUp;
928
929     if(DrawSeekGraph()) return; // [HGM] seekgraph: suppress any drawing if seek graph up
930
931     if (board == NULL) {
932         if (!lastBoardValid[nr]) return;
933         board = lastBoard[nr];
934     }
935     if (!lastBoardValid[nr] || (nr == 0 && lastFlipView != flipView)) {
936         MarkMenuItem("View.Flip View", flipView);
937     }
938
939     if(nr) { SlavePopUp(); SwitchWindow(0); } // [HGM] popup board if not yet popped up, and switch drawing to it.
940
941     /*
942      * It would be simpler to clear the window with XClearWindow()
943      * but this causes a very distracting flicker.
944      */
945
946     if (!repaint && lastBoardValid[nr] && (nr == 1 || lastFlipView == flipView)) {
947
948         /* If too much changes (begin observing new game, etc.), don't
949            do flashing */
950         do_flash = too_many_diffs(board, lastBoard[nr]) ? 0 : 1;
951
952         /* Special check for castling so we don't flash both the king
953            and the rook (just flash the king). */
954         if (do_flash) {
955             if(check_castle_draw(board, lastBoard[nr], &rrow, &rcol)) {
956                 /* Mark rook for drawing with NO flashing. */
957                 damage[nr][rrow][rcol] |= 1;
958             }
959         }
960
961         /* First pass -- Erase arrow and grid highlights, but keep square content unchanged. Except for new markers. */
962         for (i = 0; i < BOARD_HEIGHT; i++)
963           for (j = 0; j < BOARD_WIDTH; j++)
964             if (damage[nr][i][j] ||  !nr && marker[i][j] != lastMarker[i][j]) {
965                 DrawSquare(i, j, board[i][j], 0);
966                 if(lineGap && damage[nr][i][j] & 2) {
967                     drawHighlight(j, i, 0);
968                     SquareExpose(i, j, lineGap);
969                 } else SquareExpose(i, j, 0);
970                 damage[nr][i][j] = 0;
971             }
972
973         /* Second pass -- Draw (newly) empty squares
974            This prevents you from having a piece show up twice while it
975            is flashing on its new square */
976         for (i = 0; i < BOARD_HEIGHT; i++)
977           for (j = 0; j < BOARD_WIDTH; j++)
978             if (board[i][j] != lastBoard[nr][i][j] && board[i][j] == EmptySquare) {
979                 DrawSquare(i, j, board[i][j], 0);
980                 SquareExpose(i, j, 0);
981             }
982
983         /* Third pass -- Draw piece(s) in new position and flash them */
984         for (i = 0; i < BOARD_HEIGHT; i++)
985           for (j = 0; j < BOARD_WIDTH; j++)
986             if (board[i][j] != lastBoard[nr][i][j]) {
987                 DrawSquare(i, j, board[i][j], do_flash && (i != rrow || j != rcol));
988                 damage[nr][i][j] = 1; // mark for expose
989             }
990
991     } else {
992         if (lineGap > 0)
993           DrawGrid();
994
995         for (i = 0; i < BOARD_HEIGHT; i++)
996           for (j = 0; j < BOARD_WIDTH; j++) {
997               DrawSquare(i, j, board[i][j], 0);
998               damage[nr][i][j] = False;
999           }
1000
1001         exposeAll = True;
1002     }
1003
1004     CopyBoard(lastBoard[nr], board);
1005     lastBoardValid[nr] = 1;
1006   if(nr == 0) { // [HGM] dual: no highlights on second board yet
1007     lastFlipView = flipView;
1008     for (i = 0; i < BOARD_HEIGHT; i++)
1009         for (j = 0; j < BOARD_WIDTH; j++)
1010             lastMarker[i][j] = marker[i][j];
1011
1012     /* Draw highlights */
1013     if (pm1X >= 0 && pm1Y >= 0) {
1014       drawHighlight(pm1X, pm1Y, 2);
1015       if(lineGap) damage[nr][pm1Y][pm1X] |= 2;
1016     }
1017     if (pm2X >= 0 && pm2Y >= 0) {
1018       drawHighlight(pm2X, pm2Y, 2);
1019       if(lineGap) damage[nr][pm2Y][pm2X] |= 2;
1020     }
1021     if (hi1X >= 0 && hi1Y >= 0) {
1022       drawHighlight(hi1X, hi1Y, 1);
1023       if(lineGap) damage[nr][hi1Y][hi1X] |= 2;
1024     }
1025     if (hi2X >= 0 && hi2Y >= 0) {
1026       drawHighlight(hi2X, hi2Y, 1);
1027       if(lineGap) damage[nr][hi2Y][hi2X] |= 2;
1028     }
1029     DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y);
1030   }
1031   else DrawArrowHighlight (board[EP_STATUS-3], board[EP_STATUS-4], board[EP_STATUS-1], board[EP_STATUS-2]);
1032
1033     /* If piece being dragged around board, must redraw that too */
1034     DrawDragPiece();
1035
1036     if(exposeAll)
1037         GraphExpose(currBoard, 0, 0, BOARD_WIDTH*(squareSize + lineGap) + lineGap, BOARD_HEIGHT*(squareSize + lineGap) + lineGap);
1038     else {
1039         for (i = 0; i < BOARD_HEIGHT; i++)
1040             for (j = 0; j < BOARD_WIDTH; j++)
1041                 if(damage[nr][i][j]) {
1042                     if(damage[nr][i][j] & 2) // damage by old or new arrow
1043                         SquareExpose(i, j, lineGap);
1044                     else
1045                         SquareExpose(i, j, 0);
1046                     if(nr == 0) damage[nr][i][j] = 0; // on auxiliary board we retain arrow damage
1047                 }
1048     }
1049
1050     FlashDelay(0); // this flushes drawing queue;
1051     if(nr) SwitchWindow(1);
1052     else {
1053         TimeMark now;
1054         GetTimeMark(&now);
1055         if(repaint && SubtractTimeMarks(&now, &programStartTime) < 1000) {
1056             char *p = appData.message, *q;
1057             i = 0;
1058             while(*p) {
1059                 q = strchr(p, '\n');
1060                 if(q) *q = NULLCHAR;
1061                 if(!strstr(appData.suppress, p)) {
1062                     if(i == 0) DrawSeekBackground(2*squareSize, 3*squareSize, 6.5*squareSize, 5*squareSize);
1063                     DrawText(p, 2*squareSize + 5, (int) ((3 + 0.3*i++)*squareSize) + 5, 2);
1064                 }
1065                 if(q) *q++ = '\n'; else q = "";
1066                 p = q;
1067             }
1068             GraphExpose(currBoard, 2*squareSize, 3*squareSize, 4*squareSize, 2*squareSize);
1069             messedUp = TRUE;
1070         } else messedUp = FALSE;
1071     }
1072 }
1073
1074 /* [AS] Arrow highlighting support */
1075
1076 static double A_WIDTH = 5; /* Width of arrow body */
1077
1078 #define A_HEIGHT_FACTOR 6   /* Length of arrow "point", relative to body width */
1079 #define A_WIDTH_FACTOR  3   /* Width of arrow "point", relative to body width */
1080
1081 static double
1082 Sqr (double x)
1083 {
1084     return x*x;
1085 }
1086
1087 static int
1088 Round (double x)
1089 {
1090     return (int) (x + 0.5);
1091 }
1092
1093 void
1094 SquareToPos (int rank, int file, int *x, int *y)
1095 {
1096     if (flipView) {
1097         *x = lineGap + ((BOARD_WIDTH-1)-file) * (squareSize + lineGap);
1098         *y = lineGap + rank * (squareSize + lineGap);
1099     } else {
1100         *x = lineGap + file * (squareSize + lineGap);
1101         *y = lineGap + ((BOARD_HEIGHT-1)-rank) * (squareSize + lineGap);
1102     }
1103 }
1104
1105 /* Draw an arrow between two points using current settings */
1106 static void
1107 DrawArrowBetweenPoints (int s_x, int s_y, int d_x, int d_y)
1108 {
1109     Pnt arrow[8];
1110     double dx, dy, j, k, x, y;
1111
1112     if( d_x == s_x ) {
1113         int h = (d_y > s_y) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1114
1115         arrow[0].x = s_x + A_WIDTH + 0.5;
1116         arrow[0].y = s_y;
1117
1118         arrow[1].x = s_x + A_WIDTH + 0.5;
1119         arrow[1].y = d_y - h;
1120
1121         arrow[2].x = arrow[1].x + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1122         arrow[2].y = d_y - h;
1123
1124         arrow[3].x = d_x;
1125         arrow[3].y = d_y;
1126
1127         arrow[5].x = arrow[1].x - 2*A_WIDTH + 0.5;
1128         arrow[5].y = d_y - h;
1129
1130         arrow[4].x = arrow[5].x - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1131         arrow[4].y = d_y - h;
1132
1133         arrow[6].x = arrow[1].x - 2*A_WIDTH + 0.5;
1134         arrow[6].y = s_y;
1135     }
1136     else if( d_y == s_y ) {
1137         int w = (d_x > s_x) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1138
1139         arrow[0].x = s_x;
1140         arrow[0].y = s_y + A_WIDTH + 0.5;
1141
1142         arrow[1].x = d_x - w;
1143         arrow[1].y = s_y + A_WIDTH + 0.5;
1144
1145         arrow[2].x = d_x - w;
1146         arrow[2].y = arrow[1].y + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1147
1148         arrow[3].x = d_x;
1149         arrow[3].y = d_y;
1150
1151         arrow[5].x = d_x - w;
1152         arrow[5].y = arrow[1].y - 2*A_WIDTH + 0.5;
1153
1154         arrow[4].x = d_x - w;
1155         arrow[4].y = arrow[5].y - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1156
1157         arrow[6].x = s_x;
1158         arrow[6].y = arrow[1].y - 2*A_WIDTH + 0.5;
1159     }
1160     else {
1161         /* [AS] Needed a lot of paper for this! :-) */
1162         dy = (double) (d_y - s_y) / (double) (d_x - s_x);
1163         dx = (double) (s_x - d_x) / (double) (s_y - d_y);
1164
1165         j = sqrt( Sqr(A_WIDTH) / (1.0 + Sqr(dx)) );
1166
1167         k = sqrt( Sqr(A_WIDTH*A_HEIGHT_FACTOR) / (1.0 + Sqr(dy)) );
1168
1169         x = s_x;
1170         y = s_y;
1171
1172         arrow[0].x = Round(x - j);
1173         arrow[0].y = Round(y + j*dx);
1174
1175         arrow[1].x = Round(arrow[0].x + 2*j);   // [HGM] prevent width to be affected by rounding twice
1176         arrow[1].y = Round(arrow[0].y - 2*j*dx);
1177
1178         if( d_x > s_x ) {
1179             x = (double) d_x - k;
1180             y = (double) d_y - k*dy;
1181         }
1182         else {
1183             x = (double) d_x + k;
1184             y = (double) d_y + k*dy;
1185         }
1186
1187         x = Round(x); y = Round(y); // [HGM] make sure width of shaft is rounded the same way on both ends
1188
1189         arrow[6].x = Round(x - j);
1190         arrow[6].y = Round(y + j*dx);
1191
1192         arrow[2].x = Round(arrow[6].x + 2*j);
1193         arrow[2].y = Round(arrow[6].y - 2*j*dx);
1194
1195         arrow[3].x = Round(arrow[2].x + j*(A_WIDTH_FACTOR-1));
1196         arrow[3].y = Round(arrow[2].y - j*(A_WIDTH_FACTOR-1)*dx);
1197
1198         arrow[4].x = d_x;
1199         arrow[4].y = d_y;
1200
1201         arrow[5].x = Round(arrow[6].x - j*(A_WIDTH_FACTOR-1));
1202         arrow[5].y = Round(arrow[6].y + j*(A_WIDTH_FACTOR-1)*dx);
1203     }
1204
1205     DrawPolygon(arrow, 7);
1206 //    Polygon( hdc, arrow, 7 );
1207 }
1208
1209 static void
1210 ArrowDamage (int s_col, int s_row, int d_col, int d_row)
1211 {
1212     int hor, vert, i, n = partnerUp * twoBoards, delta = abs(d_row - s_row);
1213
1214     if( 2*(d_row - s_row) > abs(d_col - s_col) ) d_row = 4*d_row + 1; else 
1215     if( 2*(s_row - d_row) > abs(d_col - s_col) ) d_row = 4*d_row + 3; else d_row = 4*d_row + 2;
1216     if( 2*(d_col - s_col) > delta ) d_col = 4*d_col + 1; else 
1217     if( 2*(s_col - d_col) > delta ) d_col = 4*d_col + 3; else d_col = 4*d_col + 2;
1218     s_row = 4*s_row + 2; s_col = 4*s_col + 2;
1219
1220     hor = 64*s_col; vert = 64*s_row;
1221     for(i=0; i<= 64; i++) {
1222             damage[n][vert+30>>8][hor+30>>8] |= 2;
1223             damage[n][vert-30>>8][hor+30>>8] |= 2;
1224             damage[n][vert+30>>8][hor-30>>8] |= 2;
1225             damage[n][vert-30>>8][hor-30>>8] |= 2;
1226             hor += d_col - s_col; vert += d_row - s_row;
1227     }
1228 }
1229
1230 /* [AS] Draw an arrow between two squares */
1231 static void
1232 DrawArrowBetweenSquares (int s_col, int s_row, int d_col, int d_row)
1233 {
1234     int s_x, s_y, d_x, d_y, delta_y;
1235
1236     if( s_col == d_col && s_row == d_row ) {
1237         return;
1238     }
1239
1240     /* Get source and destination points */
1241     SquareToPos( s_row, s_col, &s_x, &s_y);
1242     SquareToPos( d_row, d_col, &d_x, &d_y);
1243     delta_y = abs(d_y - s_y);
1244
1245     if( d_y > s_y && 2*(d_y - s_y) > abs(d_x - s_x)) {
1246         d_y += squareSize / 2 - squareSize / 4; // [HGM] round towards same centers on all sides!
1247     }
1248     else if( d_y < s_y && 2*(s_y - d_y) > abs(d_x - s_x)) {
1249         d_y += squareSize / 2 + squareSize / 4;
1250     }
1251     else {
1252         d_y += squareSize / 2;
1253     }
1254
1255     if( d_x > s_x && 2*(d_x - s_x) > delta_y) {
1256         d_x += squareSize / 2 - squareSize / 4;
1257     }
1258     else if( d_x < s_x && 2*(s_x - d_x) > delta_y) {
1259         d_x += squareSize / 2 + squareSize / 4;
1260     }
1261     else {
1262         d_x += squareSize / 2;
1263     }
1264
1265     s_x += squareSize / 2;
1266     s_y += squareSize / 2;
1267
1268     /* Adjust width */
1269     A_WIDTH = squareSize / 14.; //[HGM] make float
1270
1271     DrawArrowBetweenPoints( s_x, s_y, d_x, d_y );
1272     ArrowDamage(s_col, s_row, d_col, d_row);
1273 }
1274
1275 static Boolean
1276 IsDrawArrowEnabled ()
1277 {
1278     return (appData.highlightMoveWithArrow || twoBoards && partnerUp) && squareSize >= 32;
1279 }
1280
1281 static void
1282 DrawArrowHighlight (int fromX, int fromY, int toX,int toY)
1283 {
1284     if( IsDrawArrowEnabled() && fromX >= 0 && fromY >= 0 && toX >= 0 && toY >= 0)
1285         DrawArrowBetweenSquares(fromX, fromY, toX, toY);
1286 }