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