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