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
drawHighlight(int file,int rank,int type)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
SetHighlights(int fromX,int fromY,int toX,int toY)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
ClearHighlights()181 ClearHighlights ()
182 {
183     SetHighlights(-1, -1, -1, -1);
184 }
185 
186 
187 void
SetPremoveHighlights(int fromX,int fromY,int toX,int toY)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
ClearPremoveHighlights()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
EventToSquare(int x,int limit)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
SquareColor(int row,int column)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
ScreenSquare(int column,int row,Pnt * pt,int * color)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
BoardSquare(int x,int y,int * column,int * row)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
Tween(Pnt * start,Pnt * mid,Pnt * finish,int factor,Pnt frames[],int * nFrames)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
DoSleep(int n)361 DoSleep (int n)
362 {
363     FrameDelay(n);
364 }
365 
366 static void
SetRect(MyRectangle * rect,int x,int y,int width,int height)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
Intersect(Pnt * old,Pnt * new,int size,MyRectangle * area,Pnt * pt)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
CalcUpdateRects(Pnt * old,Pnt * new,int size,MyRectangle update[],int * nUpdates)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
BeginAnimation(AnimNr anr,ChessSquare piece,ChessSquare bgPiece,int startColor,Pnt * start)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
AnimationFrame(AnimNr anr,Pnt * frame,ChessSquare piece)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
EndAnimation(AnimNr anr,Pnt * finish)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
FrameSequence(AnimNr anr,ChessSquare piece,int startColor,Pnt * start,Pnt * finish,Pnt frames[],int nFrames)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
AnimateAtomicCapture(Board board,int fromX,int fromY,int toX,int toY)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
AnimateMove(Board board,int fromX,int fromY,int toX,int toY)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] |= 2;
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
ChangeDragPiece(ChessSquare piece)655 ChangeDragPiece (ChessSquare piece)
656 {
657   anims[Player].dragPiece = piece;
658   SetDragPiece(Player, piece);
659   damage[0][fromY][fromX] = True;
660 }
661 
662 void
DragPieceMove(int x,int y)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
DragPieceEnd(int x,int y)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
DragPieceBegin(int x,int y,Boolean instantly)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
DrawDragPiece()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
DrawSquare(int row,int column,ChessSquare piece,int do_flash)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
too_many_diffs(Board b1,Board b2)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
check_castle_draw(Board newb,Board oldb,int * rrow,int * rcol)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
SquareExpose(int i,int j,int d)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
DrawPosition(int repaint,Board board)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
Sqr(double x)1082 Sqr (double x)
1083 {
1084     return x*x;
1085 }
1086 
1087 static int
Round(double x)1088 Round (double x)
1089 {
1090     return (int) (x + 0.5);
1091 }
1092 
1093 void
SquareToPos(int rank,int file,int * x,int * y)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
DrawArrowBetweenPoints(int s_x,int s_y,int d_x,int d_y)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
ArrowDamage(int s_col,int s_row,int d_col,int d_row)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
DrawArrowBetweenSquares(int s_col,int s_row,int d_col,int d_row)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
IsDrawArrowEnabled()1276 IsDrawArrowEnabled ()
1277 {
1278     return (appData.highlightMoveWithArrow || twoBoards && partnerUp) && squareSize >= 32;
1279 }
1280 
1281 static void
DrawArrowHighlight(int fromX,int fromY,int toX,int toY)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 }
1287