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