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