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