Give the dual-board option a separate board window
[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     Arg args[16];
887     int rrow, rcol;
888     int nr = twoBoards*partnerUp;
889
890     if(DrawSeekGraph()) return; // [HGM] seekgraph: suppress any drawing if seek graph up
891
892     if (board == NULL) {
893         if (!lastBoardValid[nr]) return;
894         board = lastBoard[nr];
895     }
896     if (!lastBoardValid[nr] || (nr == 0 && lastFlipView != flipView)) {
897         MarkMenuItem("View.Flip View", flipView);
898     }
899
900     if(nr) { SlavePopUp(); SwitchWindow(); } // [HGM] popup board if not yet popped up, and switch drawing to it.
901
902     /*
903      * It would be simpler to clear the window with XClearWindow()
904      * but this causes a very distracting flicker.
905      */
906
907     if (!repaint && lastBoardValid[nr] && (nr == 1 || lastFlipView == flipView)) {
908
909         if ( lineGap && IsDrawArrowEnabled())
910             DrawGrid();
911
912         /* If too much changes (begin observing new game, etc.), don't
913            do flashing */
914         do_flash = too_many_diffs(board, lastBoard[nr]) ? 0 : 1;
915
916         /* Special check for castling so we don't flash both the king
917            and the rook (just flash the king). */
918         if (do_flash) {
919             if (check_castle_draw(board, lastBoard[nr], &rrow, &rcol)) {
920                 /* Draw rook with NO flashing. King will be drawn flashing later */
921                 DrawSquare(rrow, rcol, board[rrow][rcol], 0);
922                 lastBoard[nr][rrow][rcol] = board[rrow][rcol];
923             }
924         }
925
926         /* First pass -- Draw (newly) empty squares and repair damage.
927            This prevents you from having a piece show up twice while it
928            is flashing on its new square */
929         for (i = 0; i < BOARD_HEIGHT; i++)
930           for (j = 0; j < BOARD_WIDTH; j++)
931             if ((board[i][j] != lastBoard[nr][i][j] && board[i][j] == EmptySquare)
932                 || damage[nr][i][j]) {
933                 DrawSquare(i, j, board[i][j], 0);
934                 damage[nr][i][j] = False;
935             }
936
937         /* Second pass -- Draw piece(s) in new position and flash them */
938         for (i = 0; i < BOARD_HEIGHT; i++)
939           for (j = 0; j < BOARD_WIDTH; j++)
940             if (board[i][j] != lastBoard[nr][i][j]) {
941                 DrawSquare(i, j, board[i][j], do_flash);
942             }
943     } else {
944         if (lineGap > 0)
945           DrawGrid();
946
947         for (i = 0; i < BOARD_HEIGHT; i++)
948           for (j = 0; j < BOARD_WIDTH; j++) {
949               DrawSquare(i, j, board[i][j], 0);
950               damage[nr][i][j] = False;
951           }
952     }
953
954     CopyBoard(lastBoard[nr], board);
955     lastBoardValid[nr] = 1;
956   if(nr == 0) { // [HGM] dual: no highlights on second board yet
957     lastFlipView = flipView;
958
959     /* Draw highlights */
960     if (pm1X >= 0 && pm1Y >= 0) {
961       drawHighlight(pm1X, pm1Y, 2);
962     }
963     if (pm2X >= 0 && pm2Y >= 0) {
964       drawHighlight(pm2X, pm2Y, 2);
965     }
966     if (hi1X >= 0 && hi1Y >= 0) {
967       drawHighlight(hi1X, hi1Y, 1);
968     }
969     if (hi2X >= 0 && hi2Y >= 0) {
970       drawHighlight(hi2X, hi2Y, 1);
971     }
972     DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y);
973   }
974   else DrawArrowHighlight (board[EP_STATUS-3], board[EP_STATUS-4], board[EP_STATUS-1], board[EP_STATUS-2]);
975
976     /* If piece being dragged around board, must redraw that too */
977     DrawDragPiece();
978
979     FlashDelay(0); // this flushes drawing queue;
980     if(nr) SwitchWindow();
981 }
982
983 /* [AS] Arrow highlighting support */
984
985 static double A_WIDTH = 5; /* Width of arrow body */
986
987 #define A_HEIGHT_FACTOR 6   /* Length of arrow "point", relative to body width */
988 #define A_WIDTH_FACTOR  3   /* Width of arrow "point", relative to body width */
989
990 static double
991 Sqr (double x)
992 {
993     return x*x;
994 }
995
996 static int
997 Round (double x)
998 {
999     return (int) (x + 0.5);
1000 }
1001
1002 void
1003 SquareToPos (int rank, int file, int *x, int *y)
1004 {
1005     if (flipView) {
1006         *x = lineGap + ((BOARD_WIDTH-1)-file) * (squareSize + lineGap);
1007         *y = lineGap + rank * (squareSize + lineGap);
1008     } else {
1009         *x = lineGap + file * (squareSize + lineGap);
1010         *y = lineGap + ((BOARD_HEIGHT-1)-rank) * (squareSize + lineGap);
1011     }
1012 }
1013
1014 /* Draw an arrow between two points using current settings */
1015 void
1016 DrawArrowBetweenPoints (int s_x, int s_y, int d_x, int d_y)
1017 {
1018     Pnt arrow[8];
1019     double dx, dy, j, k, x, y;
1020
1021     if( d_x == s_x ) {
1022         int h = (d_y > s_y) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1023
1024         arrow[0].x = s_x + A_WIDTH + 0.5;
1025         arrow[0].y = s_y;
1026
1027         arrow[1].x = s_x + A_WIDTH + 0.5;
1028         arrow[1].y = d_y - h;
1029
1030         arrow[2].x = arrow[1].x + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1031         arrow[2].y = d_y - h;
1032
1033         arrow[3].x = d_x;
1034         arrow[3].y = d_y;
1035
1036         arrow[5].x = arrow[1].x - 2*A_WIDTH + 0.5;
1037         arrow[5].y = d_y - h;
1038
1039         arrow[4].x = arrow[5].x - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1040         arrow[4].y = d_y - h;
1041
1042         arrow[6].x = arrow[1].x - 2*A_WIDTH + 0.5;
1043         arrow[6].y = s_y;
1044     }
1045     else if( d_y == s_y ) {
1046         int w = (d_x > s_x) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1047
1048         arrow[0].x = s_x;
1049         arrow[0].y = s_y + A_WIDTH + 0.5;
1050
1051         arrow[1].x = d_x - w;
1052         arrow[1].y = s_y + A_WIDTH + 0.5;
1053
1054         arrow[2].x = d_x - w;
1055         arrow[2].y = arrow[1].y + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1056
1057         arrow[3].x = d_x;
1058         arrow[3].y = d_y;
1059
1060         arrow[5].x = d_x - w;
1061         arrow[5].y = arrow[1].y - 2*A_WIDTH + 0.5;
1062
1063         arrow[4].x = d_x - w;
1064         arrow[4].y = arrow[5].y - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1065
1066         arrow[6].x = s_x;
1067         arrow[6].y = arrow[1].y - 2*A_WIDTH + 0.5;
1068     }
1069     else {
1070         /* [AS] Needed a lot of paper for this! :-) */
1071         dy = (double) (d_y - s_y) / (double) (d_x - s_x);
1072         dx = (double) (s_x - d_x) / (double) (s_y - d_y);
1073
1074         j = sqrt( Sqr(A_WIDTH) / (1.0 + Sqr(dx)) );
1075
1076         k = sqrt( Sqr(A_WIDTH*A_HEIGHT_FACTOR) / (1.0 + Sqr(dy)) );
1077
1078         x = s_x;
1079         y = s_y;
1080
1081         arrow[0].x = Round(x - j);
1082         arrow[0].y = Round(y + j*dx);
1083
1084         arrow[1].x = Round(arrow[0].x + 2*j);   // [HGM] prevent width to be affected by rounding twice
1085         arrow[1].y = Round(arrow[0].y - 2*j*dx);
1086
1087         if( d_x > s_x ) {
1088             x = (double) d_x - k;
1089             y = (double) d_y - k*dy;
1090         }
1091         else {
1092             x = (double) d_x + k;
1093             y = (double) d_y + k*dy;
1094         }
1095
1096         x = Round(x); y = Round(y); // [HGM] make sure width of shaft is rounded the same way on both ends
1097
1098         arrow[6].x = Round(x - j);
1099         arrow[6].y = Round(y + j*dx);
1100
1101         arrow[2].x = Round(arrow[6].x + 2*j);
1102         arrow[2].y = Round(arrow[6].y - 2*j*dx);
1103
1104         arrow[3].x = Round(arrow[2].x + j*(A_WIDTH_FACTOR-1));
1105         arrow[3].y = Round(arrow[2].y - j*(A_WIDTH_FACTOR-1)*dx);
1106
1107         arrow[4].x = d_x;
1108         arrow[4].y = d_y;
1109
1110         arrow[5].x = Round(arrow[6].x - j*(A_WIDTH_FACTOR-1));
1111         arrow[5].y = Round(arrow[6].y + j*(A_WIDTH_FACTOR-1)*dx);
1112     }
1113
1114     DrawPolygon(arrow, 7);
1115 //    Polygon( hdc, arrow, 7 );
1116 }
1117
1118 void
1119 ArrowDamage (int s_col, int s_row, int d_col, int d_row)
1120 {
1121     int hor, vert, i, n = partnerUp * twoBoards;
1122     hor = 64*s_col + 32; vert = 64*s_row + 32;
1123     for(i=0; i<= 64; i++) {
1124             damage[n][vert+6>>6][hor+6>>6] = True;
1125             damage[n][vert-6>>6][hor+6>>6] = True;
1126             damage[n][vert+6>>6][hor-6>>6] = True;
1127             damage[n][vert-6>>6][hor-6>>6] = True;
1128             hor += d_col - s_col; vert += d_row - s_row;
1129     }
1130 }
1131
1132 /* [AS] Draw an arrow between two squares */
1133 void
1134 DrawArrowBetweenSquares (int s_col, int s_row, int d_col, int d_row)
1135 {
1136     int s_x, s_y, d_x, d_y;
1137
1138     if( s_col == d_col && s_row == d_row ) {
1139         return;
1140     }
1141
1142     /* Get source and destination points */
1143     SquareToPos( s_row, s_col, &s_x, &s_y);
1144     SquareToPos( d_row, d_col, &d_x, &d_y);
1145
1146     if( d_y > s_y ) {
1147         d_y += squareSize / 2 - squareSize / 4; // [HGM] round towards same centers on all sides!
1148     }
1149     else if( d_y < s_y ) {
1150         d_y += squareSize / 2 + squareSize / 4;
1151     }
1152     else {
1153         d_y += squareSize / 2;
1154     }
1155
1156     if( d_x > s_x ) {
1157         d_x += squareSize / 2 - squareSize / 4;
1158     }
1159     else if( d_x < s_x ) {
1160         d_x += squareSize / 2 + squareSize / 4;
1161     }
1162     else {
1163         d_x += squareSize / 2;
1164     }
1165
1166     s_x += squareSize / 2;
1167     s_y += squareSize / 2;
1168
1169     /* Adjust width */
1170     A_WIDTH = squareSize / 14.; //[HGM] make float
1171
1172     DrawArrowBetweenPoints( s_x, s_y, d_x, d_y );
1173     ArrowDamage(s_col, s_row, d_col, d_row);
1174 }
1175
1176 Boolean
1177 IsDrawArrowEnabled ()
1178 {
1179     return (appData.highlightMoveWithArrow || twoBoards && partnerUp) && squareSize >= 32;
1180 }
1181
1182 void
1183 DrawArrowHighlight (int fromX, int fromY, int toX,int toY)
1184 {
1185     if( IsDrawArrowEnabled() && fromX >= 0 && fromY >= 0 && toX >= 0 && toY >= 0)
1186         DrawArrowBetweenSquares(fromX, fromY, toX, toY);
1187 }
1188
1189