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