New browser
[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, hOffset;
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     if(twoBoards && partnerUp) x += hOffset; // [HGM] dual: draw second board
782
783     square_color = SquareColor(row, column);
784
785     string[1] = NULLCHAR;
786     if (appData.showCoords && row == (flipView ? BOARD_HEIGHT-1 : 0)
787                 && column >= BOARD_LEFT && column < BOARD_RGHT) {
788         string[0] = 'a' + column - BOARD_LEFT;
789         align = 1; // coord in lower-right corner
790     }
791     if (appData.showCoords && column == (flipView ? BOARD_RGHT-1 : BOARD_LEFT)) {
792         string[0] = ONE + row;
793         align = 2; // coord in upper-left corner
794     }
795     if (column == (flipView ? BOARD_LEFT-1 : BOARD_RGHT) && piece > 1 ) {
796         string[0] = '0' + piece;
797         align = 3; // holdings count in upper-right corner
798     }
799     if (column == (flipView ? BOARD_RGHT : BOARD_LEFT-1) && piece > 1) {
800         string[0] = '0' + piece;
801         align = 4; // holdings count in upper-left corner
802     }
803     if(square_color == 2 || appData.blindfold) piece = EmptySquare;
804
805     if (do_flash && piece != EmptySquare && appData.flashCount > 0) {
806         for (i=0; i<appData.flashCount; ++i) {
807             DrawOneSquare(x, y, piece, square_color, 0, string, 0);
808             FlashDelay(flash_delay);
809             DrawOneSquare(x, y, EmptySquare, square_color, 0, string, 0);
810             FlashDelay(flash_delay);
811         }
812     }
813     DrawOneSquare(x, y, piece, square_color, partnerUp ? 0 : marker[row][column], string, align);
814 }
815
816 /* Returns 1 if there are "too many" differences between b1 and b2
817    (i.e. more than 1 move was made) */
818 static int
819 too_many_diffs (Board b1, Board b2)
820 {
821     int i, j;
822     int c = 0;
823
824     for (i=0; i<BOARD_HEIGHT; ++i) {
825         for (j=0; j<BOARD_WIDTH; ++j) {
826             if (b1[i][j] != b2[i][j]) {
827                 if (++c > 4)    /* Castling causes 4 diffs */
828                   return 1;
829             }
830         }
831     }
832     return 0;
833 }
834
835 /* Matrix describing castling maneuvers */
836 /* Row, ColRookFrom, ColKingFrom, ColRookTo, ColKingTo */
837 static int castling_matrix[4][5] = {
838     { 0, 0, 4, 3, 2 },          /* 0-0-0, white */
839     { 0, 7, 4, 5, 6 },          /* 0-0,   white */
840     { 7, 0, 4, 3, 2 },          /* 0-0-0, black */
841     { 7, 7, 4, 5, 6 }           /* 0-0,   black */
842 };
843
844 /* Checks whether castling occurred. If it did, *rrow and *rcol
845    are set to the destination (row,col) of the rook that moved.
846
847    Returns 1 if castling occurred, 0 if not.
848
849    Note: Only handles a max of 1 castling move, so be sure
850    to call too_many_diffs() first.
851    */
852 static int
853 check_castle_draw (Board newb, Board oldb, int *rrow, int *rcol)
854 {
855     int i, *r, j;
856     int match;
857
858     /* For each type of castling... */
859     for (i=0; i<4; ++i) {
860         r = castling_matrix[i];
861
862         /* Check the 4 squares involved in the castling move */
863         match = 0;
864         for (j=1; j<=4; ++j) {
865             if (newb[r[0]][r[j]] == oldb[r[0]][r[j]]) {
866                 match = 1;
867                 break;
868             }
869         }
870
871         if (!match) {
872             /* All 4 changed, so it must be a castling move */
873             *rrow = r[0];
874             *rcol = r[3];
875             return 1;
876         }
877     }
878     return 0;
879 }
880
881 void
882 DrawPosition (int repaint, Board board)
883 {
884     int i, j, do_flash;
885     static int lastFlipView = 0;
886     static int lastBoardValid[2] = {0, 0};
887     static Board lastBoard[2];
888     Arg args[16];
889     int rrow, rcol;
890     int nr = twoBoards*partnerUp;
891
892     if(DrawSeekGraph()) return; // [HGM] seekgraph: suppress any drawing if seek graph up
893
894     if (board == NULL) {
895         if (!lastBoardValid[nr]) return;
896         board = lastBoard[nr];
897     }
898     if (!lastBoardValid[nr] || (nr == 0 && lastFlipView != flipView)) {
899         MarkMenuItem("View.Flip View", flipView);
900     }
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(0);
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(twoBoards & partnerUp);
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     /* If piece being dragged around board, must redraw that too */
975     DrawDragPiece();
976
977     FlashDelay(0); // this flushes drawing queue;
978 }
979
980 /* [AS] Arrow highlighting support */
981
982 static double A_WIDTH = 5; /* Width of arrow body */
983
984 #define A_HEIGHT_FACTOR 6   /* Length of arrow "point", relative to body width */
985 #define A_WIDTH_FACTOR  3   /* Width of arrow "point", relative to body width */
986
987 static double
988 Sqr (double x)
989 {
990     return x*x;
991 }
992
993 static int
994 Round (double x)
995 {
996     return (int) (x + 0.5);
997 }
998
999 void
1000 SquareToPos (int rank, int file, int *x, int *y)
1001 {
1002     if (flipView) {
1003         *x = lineGap + ((BOARD_WIDTH-1)-file) * (squareSize + lineGap);
1004         *y = lineGap + rank * (squareSize + lineGap);
1005     } else {
1006         *x = lineGap + file * (squareSize + lineGap);
1007         *y = lineGap + ((BOARD_HEIGHT-1)-rank) * (squareSize + lineGap);
1008     }
1009 }
1010
1011 /* Draw an arrow between two points using current settings */
1012 void
1013 DrawArrowBetweenPoints (int s_x, int s_y, int d_x, int d_y)
1014 {
1015     Pnt arrow[8];
1016     double dx, dy, j, k, x, y;
1017
1018     if( d_x == s_x ) {
1019         int h = (d_y > s_y) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1020
1021         arrow[0].x = s_x + A_WIDTH + 0.5;
1022         arrow[0].y = s_y;
1023
1024         arrow[1].x = s_x + A_WIDTH + 0.5;
1025         arrow[1].y = d_y - h;
1026
1027         arrow[2].x = arrow[1].x + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1028         arrow[2].y = d_y - h;
1029
1030         arrow[3].x = d_x;
1031         arrow[3].y = d_y;
1032
1033         arrow[5].x = arrow[1].x - 2*A_WIDTH + 0.5;
1034         arrow[5].y = d_y - h;
1035
1036         arrow[4].x = arrow[5].x - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1037         arrow[4].y = d_y - h;
1038
1039         arrow[6].x = arrow[1].x - 2*A_WIDTH + 0.5;
1040         arrow[6].y = s_y;
1041     }
1042     else if( d_y == s_y ) {
1043         int w = (d_x > s_x) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1044
1045         arrow[0].x = s_x;
1046         arrow[0].y = s_y + A_WIDTH + 0.5;
1047
1048         arrow[1].x = d_x - w;
1049         arrow[1].y = s_y + A_WIDTH + 0.5;
1050
1051         arrow[2].x = d_x - w;
1052         arrow[2].y = arrow[1].y + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1053
1054         arrow[3].x = d_x;
1055         arrow[3].y = d_y;
1056
1057         arrow[5].x = d_x - w;
1058         arrow[5].y = arrow[1].y - 2*A_WIDTH + 0.5;
1059
1060         arrow[4].x = d_x - w;
1061         arrow[4].y = arrow[5].y - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1062
1063         arrow[6].x = s_x;
1064         arrow[6].y = arrow[1].y - 2*A_WIDTH + 0.5;
1065     }
1066     else {
1067         /* [AS] Needed a lot of paper for this! :-) */
1068         dy = (double) (d_y - s_y) / (double) (d_x - s_x);
1069         dx = (double) (s_x - d_x) / (double) (s_y - d_y);
1070
1071         j = sqrt( Sqr(A_WIDTH) / (1.0 + Sqr(dx)) );
1072
1073         k = sqrt( Sqr(A_WIDTH*A_HEIGHT_FACTOR) / (1.0 + Sqr(dy)) );
1074
1075         x = s_x;
1076         y = s_y;
1077
1078         arrow[0].x = Round(x - j);
1079         arrow[0].y = Round(y + j*dx);
1080
1081         arrow[1].x = Round(arrow[0].x + 2*j);   // [HGM] prevent width to be affected by rounding twice
1082         arrow[1].y = Round(arrow[0].y - 2*j*dx);
1083
1084         if( d_x > s_x ) {
1085             x = (double) d_x - k;
1086             y = (double) d_y - k*dy;
1087         }
1088         else {
1089             x = (double) d_x + k;
1090             y = (double) d_y + k*dy;
1091         }
1092
1093         x = Round(x); y = Round(y); // [HGM] make sure width of shaft is rounded the same way on both ends
1094
1095         arrow[6].x = Round(x - j);
1096         arrow[6].y = Round(y + j*dx);
1097
1098         arrow[2].x = Round(arrow[6].x + 2*j);
1099         arrow[2].y = Round(arrow[6].y - 2*j*dx);
1100
1101         arrow[3].x = Round(arrow[2].x + j*(A_WIDTH_FACTOR-1));
1102         arrow[3].y = Round(arrow[2].y - j*(A_WIDTH_FACTOR-1)*dx);
1103
1104         arrow[4].x = d_x;
1105         arrow[4].y = d_y;
1106
1107         arrow[5].x = Round(arrow[6].x - j*(A_WIDTH_FACTOR-1));
1108         arrow[5].y = Round(arrow[6].y + j*(A_WIDTH_FACTOR-1)*dx);
1109     }
1110
1111     DrawPolygon(arrow, 7);
1112 //    Polygon( hdc, arrow, 7 );
1113 }
1114
1115 void
1116 ArrowDamage (int s_col, int s_row, int d_col, int d_row)
1117 {
1118     int hor, vert, i;
1119     hor = 64*s_col + 32; vert = 64*s_row + 32;
1120     for(i=0; i<= 64; i++) {
1121             damage[0][vert+6>>6][hor+6>>6] = True;
1122             damage[0][vert-6>>6][hor+6>>6] = True;
1123             damage[0][vert+6>>6][hor-6>>6] = True;
1124             damage[0][vert-6>>6][hor-6>>6] = True;
1125             hor += d_col - s_col; vert += d_row - s_row;
1126     }
1127 }
1128
1129 /* [AS] Draw an arrow between two squares */
1130 void
1131 DrawArrowBetweenSquares (int s_col, int s_row, int d_col, int d_row)
1132 {
1133     int s_x, s_y, d_x, d_y;
1134
1135     if( s_col == d_col && s_row == d_row ) {
1136         return;
1137     }
1138
1139     /* Get source and destination points */
1140     SquareToPos( s_row, s_col, &s_x, &s_y);
1141     SquareToPos( d_row, d_col, &d_x, &d_y);
1142
1143     if( d_y > s_y ) {
1144         d_y += squareSize / 2 - squareSize / 4; // [HGM] round towards same centers on all sides!
1145     }
1146     else if( d_y < s_y ) {
1147         d_y += squareSize / 2 + squareSize / 4;
1148     }
1149     else {
1150         d_y += squareSize / 2;
1151     }
1152
1153     if( d_x > s_x ) {
1154         d_x += squareSize / 2 - squareSize / 4;
1155     }
1156     else if( d_x < s_x ) {
1157         d_x += squareSize / 2 + squareSize / 4;
1158     }
1159     else {
1160         d_x += squareSize / 2;
1161     }
1162
1163     s_x += squareSize / 2;
1164     s_y += squareSize / 2;
1165
1166     /* Adjust width */
1167     A_WIDTH = squareSize / 14.; //[HGM] make float
1168
1169     DrawArrowBetweenPoints( s_x, s_y, d_x, d_y );
1170     ArrowDamage(s_col, s_row, d_col, d_row);
1171 }
1172
1173 Boolean
1174 IsDrawArrowEnabled ()
1175 {
1176     return appData.highlightMoveWithArrow && squareSize >= 32;
1177 }
1178
1179 void
1180 DrawArrowHighlight (int fromX, int fromY, int toX,int toY)
1181 {
1182     if( IsDrawArrowEnabled() && fromX >= 0 && fromY >= 0 && toX >= 0 && toY >= 0)
1183         DrawArrowBetweenSquares(fromX, fromY, toX, toY);
1184 }
1185
1186