Fix Seirawan reverse-castling animation
[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, 2013, 2014 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   AnimState *anim = &anims[anr];
483   int     count, i, x, y, w, h;
484
485   /* Save what we are about to draw into the new buffer */
486   CopyRectangle(anr, DISP, 0,
487             x = frame->x, y = frame->y, w = squareSize, h = 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     /* [HGM] correct expose rectangle to encompass both overlapping squares */
507     if(x > anim->prevFrame.x) w += x - anim->prevFrame.x, x = anim->prevFrame.x;
508     else  w += anim->prevFrame.x - x;
509     if(y > anim->prevFrame.y) h += y - anim->prevFrame.y, y = anim->prevFrame.y;
510     else  h += anim->prevFrame.y - y;
511   } else {
512     /* Easy when no overlap */
513     CopyRectangle(anr, 2, DISP,
514                   0, 0, squareSize, squareSize,
515                   anim->prevFrame.x, anim->prevFrame.y);
516     GraphExpose(currBoard, anim->prevFrame.x, anim->prevFrame.y, squareSize, squareSize);
517   }
518
519   /* Save this frame for next time round */
520   CopyRectangle(anr, 0, 2,
521                 0, 0, squareSize, squareSize,
522                 0, 0);
523   anim->prevFrame = *frame;
524
525   /* Draw piece over original screen contents, not current,
526      and copy entire rect. Wipes out overlapping piece images. */
527   InsertPiece(anr, piece);
528   CopyRectangle(anr, 0, DISP,
529                 0, 0, squareSize, squareSize,
530                 frame->x, frame->y);
531   GraphExpose(currBoard, x, y, w, h);
532 }
533
534 static void
535 EndAnimation (AnimNr anr, Pnt *finish)
536 {
537   MyRectangle updates[4];
538   MyRectangle overlap;
539   Pnt     pt;
540   int        count, i;
541   AnimState *anim = &anims[anr];
542
543   /* The main code will redraw the final square, so we
544      only need to erase the bits that don't overlap.    */
545   if (Intersect(&anim->prevFrame, finish, squareSize, &overlap, &pt)) {
546     CalcUpdateRects(&anim->prevFrame, finish, squareSize, updates, &count);
547     for (i = 0; i < count; i++)
548       CopyRectangle(anr, 2, DISP,
549                 updates[i].x - anim->prevFrame.x,
550                 updates[i].y - anim->prevFrame.y,
551                 updates[i].width, updates[i].height,
552                 updates[i].x, updates[i].y);
553   } else {
554     CopyRectangle(anr, 2, DISP,
555                 0, 0, squareSize, squareSize,
556                 anim->prevFrame.x, anim->prevFrame.y);
557   }
558 }
559
560 static void
561 FrameSequence (AnimNr anr, ChessSquare piece, int startColor, Pnt *start, Pnt *finish, Pnt frames[], int nFrames)
562 {
563   int n;
564
565   BeginAnimation(anr, piece, EmptySquare, startColor, start);
566   for (n = 0; n < nFrames; n++) {
567     AnimationFrame(anr, &(frames[n]), piece);
568     FrameDelay(appData.animSpeed);
569   }
570   EndAnimation(anr, finish);
571 }
572
573 void
574 AnimateAtomicCapture (Board board, int fromX, int fromY, int toX, int toY)
575 {
576     int i, x, y;
577     ChessSquare piece = board[fromY][toY];
578     board[fromY][toY] = EmptySquare;
579     DrawPosition(FALSE, board);
580     if (flipView) {
581         x = lineGap + ((BOARD_WIDTH-1)-toX) * (squareSize + lineGap);
582         y = lineGap + toY * (squareSize + lineGap);
583     } else {
584         x = lineGap + toX * (squareSize + lineGap);
585         y = lineGap + ((BOARD_HEIGHT-1)-toY) * (squareSize + lineGap);
586     }
587     for(i=1; i<4*kFactor; i++) {
588         int r = squareSize * 9 * i/(20*kFactor - 5);
589         DrawDot(1, x + squareSize/2 - r, y+squareSize/2 - r, 2*r);
590         FrameDelay(appData.animSpeed);
591     }
592     board[fromY][toY] = piece;
593     DrawGrid();
594 }
595
596 /* Main control logic for deciding what to animate and how */
597
598 void
599 AnimateMove (Board board, int fromX, int fromY, int toX, int toY)
600 {
601   ChessSquare piece;
602   int hop, x = toX, y = toY;
603   Pnt      start, finish, mid;
604   Pnt      frames[kFactor * 2 + 1];
605   int         nFrames, startColor, endColor;
606
607   if(killX >= 0 && IS_LION(board[fromY][fromX])) Roar();
608
609   /* Are we animating? */
610   if (!appData.animate || appData.blindfold)
611     return;
612
613   if(board[toY][toX] == WhiteRook && board[fromY][fromX] == WhiteKing ||
614      board[toY][toX] == BlackRook && board[fromY][fromX] == BlackKing ||
615      board[toY][toX] == WhiteKing && board[fromY][fromX] == WhiteRook || // [HGM] seirawan
616      board[toY][toX] == BlackKing && board[fromY][fromX] == BlackRook)
617         return; // [HGM] FRC: no animtion of FRC castlings, as to-square is not true to-square
618
619   if (fromY < 0 || fromX < 0 || toX < 0 || toY < 0) return;
620   piece = board[fromY][fromX];
621   if (piece >= EmptySquare) return;
622
623   if(killX >= 0) toX = killX, toY = killY; // [HGM] lion: first to kill square
624
625 again:
626
627 #if DONT_HOP
628   hop = FALSE;
629 #else
630   hop = abs(fromX-toX) == 1 && abs(fromY-toY) == 2 || abs(fromX-toX) == 2 && abs(fromY-toY) == 1;
631 #endif
632
633   ScreenSquare(fromX, fromY, &start, &startColor);
634   ScreenSquare(toX, toY, &finish, &endColor);
635
636   if (hop) {
637     /* Knight: make straight movement then diagonal */
638     if (abs(toY - fromY) < abs(toX - fromX)) {
639        mid.x = start.x + (finish.x - start.x) / 2;
640        mid.y = start.y;
641      } else {
642        mid.x = start.x;
643        mid.y = start.y + (finish.y - start.y) / 2;
644      }
645   } else {
646     mid.x = start.x + (finish.x - start.x) / 2;
647     mid.y = start.y + (finish.y - start.y) / 2;
648   }
649
650   /* Don't use as many frames for very short moves */
651   if (abs(toY - fromY) + abs(toX - fromX) <= 2)
652     Tween(&start, &mid, &finish, kFactor - 1, frames, &nFrames);
653   else
654     Tween(&start, &mid, &finish, kFactor, frames, &nFrames);
655   FrameSequence(Game, piece, startColor, &start, &finish, frames, nFrames);
656   if(Explode(board, fromX, fromY, toX, toY)) { // mark as damaged
657     int i,j;
658     for(i=0; i<BOARD_WIDTH; i++) for(j=0; j<BOARD_HEIGHT; j++)
659       if((i-toX)*(i-toX) + (j-toY)*(j-toY) < 6) damage[0][j][i] |=  1 + ((i-toX ^ j-toY) & 1);
660   }
661
662   /* Be sure end square is redrawn */
663   damage[0][toY][toX] |= True;
664
665   if(toX != x || toY != y) { fromX = toX; fromY = toY; toX = x; toY = y; goto again; } // second leg
666 }
667
668 void
669 ChangeDragPiece (ChessSquare piece)
670 {
671   anims[Player].dragPiece = piece;
672   SetDragPiece(Player, piece);
673 }
674
675 void
676 DragPieceMove (int x, int y)
677 {
678     Pnt corner;
679
680     /* Are we animating? */
681     if (!appData.animateDragging || appData.blindfold)
682       return;
683
684     /* Sanity check */
685     if (! anims[Player].dragActive)
686       return;
687     /* Move piece, maintaining same relative position
688        of mouse within square    */
689     corner.x = x - anims[Player].mouseDelta.x;
690     corner.y = y - anims[Player].mouseDelta.y;
691     AnimationFrame(Player, &corner, anims[Player].dragPiece);
692 #if HIGHDRAG*0
693     if (appData.highlightDragging) {
694         int boardX, boardY;
695         BoardSquare(x, y, &boardX, &boardY);
696         SetHighlights(fromX, fromY, boardX, boardY);
697     }
698 #endif
699 }
700
701 void
702 DragPieceEnd (int x, int y)
703 {
704     int boardX, boardY, color;
705     Pnt corner;
706
707     /* Are we animating? */
708     if (!appData.animateDragging || appData.blindfold)
709       return;
710
711     /* Sanity check */
712     if (! anims[Player].dragActive)
713       return;
714     /* Last frame in sequence is square piece is
715        placed on, which may not match mouse exactly. */
716     BoardSquare(x, y, &boardX, &boardY);
717     ScreenSquare(boardX, boardY, &corner, &color);
718     EndAnimation(Player, &corner);
719
720     /* Be sure end square is redrawn */
721     damage[0][boardY][boardX] = True;
722
723     /* This prevents weird things happening with fast successive
724        clicks which on my Sun at least can cause motion events
725        without corresponding press/release. */
726     anims[Player].dragActive = False;
727 }
728
729 void
730 DragPieceBegin (int x, int y, Boolean instantly)
731 {
732     int  boardX, boardY, color;
733     Pnt corner;
734
735     /* Are we animating? */
736     if (!appData.animateDragging || appData.blindfold)
737       return;
738
739     /* Figure out which square we start in and the
740        mouse position relative to top left corner. */
741     BoardSquare(x, y, &boardX, &boardY);
742     anims[Player].startBoardX = boardX;
743     anims[Player].startBoardY = boardY;
744     ScreenSquare(boardX, boardY, &corner, &color);
745     anims[Player].startSquare  = corner;
746     anims[Player].startColor   = color;
747     /* As soon as we start dragging, the piece will jump slightly to
748        be centered over the mouse pointer. */
749     anims[Player].mouseDelta.x = squareSize/2;
750     anims[Player].mouseDelta.y = squareSize/2;
751     /* Initialise animation */
752     anims[Player].dragPiece = PieceForSquare(boardX, boardY);
753     /* Sanity check */
754     if (anims[Player].dragPiece >= 0 && anims[Player].dragPiece < EmptySquare) {
755         ChessSquare bgPiece = EmptySquare;
756         anims[Player].dragActive = True;
757         if(boardX == BOARD_RGHT+1 && PieceForSquare(boardX-1, boardY) > 1 ||
758            boardX == BOARD_LEFT-2 && PieceForSquare(boardX+1, boardY) > 1)
759             bgPiece = anims[Player].dragPiece;
760         if(gatingPiece != EmptySquare) bgPiece = gatingPiece;
761         BeginAnimation(Player, anims[Player].dragPiece, bgPiece, color, &corner);
762         /* Mark this square as needing to be redrawn. Note that
763            we don't remove the piece though, since logically (ie
764            as seen by opponent) the move hasn't been made yet. */
765         damage[0][boardY][boardX] = True;
766     } else {
767         anims[Player].dragActive = False;
768     }
769 }
770
771 /* Handle expose event while piece being dragged */
772
773 static void
774 DrawDragPiece ()
775 {
776   if (!anims[Player].dragActive || appData.blindfold)
777     return;
778
779   /* What we're doing: logically, the move hasn't been made yet,
780      so the piece is still in it's original square. But visually
781      it's being dragged around the board. So we erase the square
782      that the piece is on and draw it at the last known drag point. */
783   DrawOneSquare(anims[Player].startSquare.x, anims[Player].startSquare.y,
784                 EmptySquare, anims[Player].startColor, 0, NULL, NULL, 0);
785   AnimationFrame(Player, &anims[Player].prevFrame, anims[Player].dragPiece);
786   damage[0][anims[Player].startBoardY][anims[Player].startBoardX] = TRUE;
787 }
788
789 static void
790 DrawSquare (int row, int column, ChessSquare piece, int do_flash)
791 {
792     int square_color, x, y, align=0;
793     int i;
794     char tString[3], bString[2];
795     int flash_delay;
796
797     /* Calculate delay in milliseconds (2-delays per complete flash) */
798     flash_delay = 500 / appData.flashRate;
799
800     if (flipView) {
801         x = lineGap + ((BOARD_WIDTH-1)-column) *
802           (squareSize + lineGap);
803         y = lineGap + row * (squareSize + lineGap);
804     } else {
805         x = lineGap + column * (squareSize + lineGap);
806         y = lineGap + ((BOARD_HEIGHT-1)-row) *
807           (squareSize + lineGap);
808     }
809
810     square_color = SquareColor(row, column);
811
812     bString[1] = bString[0] = NULLCHAR;
813     if (appData.showCoords && row == (flipView ? BOARD_HEIGHT-1 : 0)
814                 && column >= BOARD_LEFT && column < BOARD_RGHT) {
815         bString[0] = 'a' + column - BOARD_LEFT;
816         align = 1; // coord in lower-right corner
817     }
818     if (appData.showCoords && column == (flipView ? BOARD_RGHT-1 : BOARD_LEFT)) {
819         snprintf(tString, 3, "%d", ONE - '0' + row);
820         align = 2; // coord in upper-left corner
821     }
822     if (column == (flipView ? BOARD_LEFT-1 : BOARD_RGHT) && piece > 1 ) {
823         snprintf(tString, 3, "%d", piece);
824         align = 3; // holdings count in upper-right corner
825     }
826     if (column == (flipView ? BOARD_RGHT : BOARD_LEFT-1) && piece > 1) {
827         snprintf(tString, 3, "%d", piece);
828         align = 4; // holdings count in upper-left corner
829     }
830     if(piece == DarkSquare) square_color = 2;
831     if(square_color == 2 || appData.blindfold) piece = EmptySquare;
832
833     if (do_flash && piece != EmptySquare && appData.flashCount > 0) {
834         for (i=0; i<appData.flashCount; ++i) {
835             DrawOneSquare(x, y, piece, square_color, 0, tString, bString, 0);
836             GraphExpose(currBoard, x, y, squareSize, squareSize);
837             FlashDelay(flash_delay);
838             DrawOneSquare(x, y, EmptySquare, square_color, 0, tString, bString, 0);
839             GraphExpose(currBoard, x, y, squareSize, squareSize);
840             FlashDelay(flash_delay);
841         }
842     }
843     DrawOneSquare(x, y, piece, square_color, partnerUp ? 0 : marker[row][column], tString, bString, align);
844 }
845
846 /* Returns 1 if there are "too many" differences between b1 and b2
847    (i.e. more than 1 move was made) */
848 static int
849 too_many_diffs (Board b1, Board b2)
850 {
851     int i, j;
852     int c = 0;
853
854     for (i=0; i<BOARD_HEIGHT; ++i) {
855         for (j=0; j<BOARD_WIDTH; ++j) {
856             if (b1[i][j] != b2[i][j]) {
857                 if (++c > 4)    /* Castling causes 4 diffs */
858                   return 1;
859             }
860         }
861     }
862     return 0;
863 }
864
865 /* Matrix describing castling maneuvers */
866 /* Row, ColRookFrom, ColKingFrom, ColRookTo, ColKingTo */
867 static int castling_matrix[4][5] = {
868     { 0, 0, 4, 3, 2 },          /* 0-0-0, white */
869     { 0, 7, 4, 5, 6 },          /* 0-0,   white */
870     { 7, 0, 4, 3, 2 },          /* 0-0-0, black */
871     { 7, 7, 4, 5, 6 }           /* 0-0,   black */
872 };
873
874 /* Checks whether castling occurred. If it did, *rrow and *rcol
875    are set to the destination (row,col) of the rook that moved.
876
877    Returns 1 if castling occurred, 0 if not.
878
879    Note: Only handles a max of 1 castling move, so be sure
880    to call too_many_diffs() first.
881    */
882 static int
883 check_castle_draw (Board newb, Board oldb, int *rrow, int *rcol)
884 {
885     int i, *r, j;
886     int match;
887
888     /* For each type of castling... */
889     for (i=0; i<4; ++i) {
890         r = castling_matrix[i];
891
892         /* Check the 4 squares involved in the castling move */
893         match = 0;
894         for (j=1; j<=4; ++j) {
895             if (newb[r[0]][r[j]] == oldb[r[0]][r[j]]) {
896                 match = 1;
897                 break;
898             }
899         }
900
901         if (!match) {
902             /* All 4 changed, so it must be a castling move */
903             *rrow = r[0];
904             *rcol = r[3];
905             return 1;
906         }
907     }
908     return 0;
909 }
910
911 void
912 DrawPosition (int repaint, Board board)
913 {
914     int i, j, do_flash, exposeAll = False;
915     static int lastFlipView = 0;
916     static int lastBoardValid[2] = {0, 0};
917     static Board lastBoard[2];
918     static char lastMarker[BOARD_RANKS][BOARD_FILES];
919     int rrow, rcol;
920     int nr = twoBoards*partnerUp;
921
922     if(DrawSeekGraph()) return; // [HGM] seekgraph: suppress any drawing if seek graph up
923
924     if (board == NULL) {
925         if (!lastBoardValid[nr]) return;
926         board = lastBoard[nr];
927     }
928     if (!lastBoardValid[nr] || (nr == 0 && lastFlipView != flipView)) {
929         MarkMenuItem("View.Flip View", flipView);
930     }
931
932     if(nr) { SlavePopUp(); SwitchWindow(0); } // [HGM] popup board if not yet popped up, and switch drawing to it.
933
934     /*
935      * It would be simpler to clear the window with XClearWindow()
936      * but this causes a very distracting flicker.
937      */
938
939     if (!repaint && lastBoardValid[nr] && (nr == 1 || lastFlipView == flipView)) {
940
941 //      if ( lineGap && IsDrawArrowEnabled())
942 //          DrawGrid();
943
944         /* If too much changes (begin observing new game, etc.), don't
945            do flashing */
946         do_flash = too_many_diffs(board, lastBoard[nr]) ? 0 : 1;
947
948         /* Special check for castling so we don't flash both the king
949            and the rook (just flash the king). */
950         if (do_flash) {
951             if (check_castle_draw(board, lastBoard[nr], &rrow, &rcol)) {
952                 /* Mark rook for drawing with NO flashing. */
953                 damage[nr][rrow][rcol] |= 1;
954             }
955         }
956
957         /* First pass -- Draw (newly) empty squares and repair damage.
958            This prevents you from having a piece show up twice while it
959            is flashing on its new square */
960         for (i = 0; i < BOARD_HEIGHT; i++)
961           for (j = 0; j < BOARD_WIDTH; j++)
962             if (((board[i][j] != lastBoard[nr][i][j] || !nr && marker[i][j] != lastMarker[i][j]) && board[i][j] == EmptySquare)
963                 || damage[nr][i][j]) {
964                 DrawSquare(i, j, board[i][j], 0);
965                 if(damage[nr][i][j] & 2) {
966                     drawHighlight(j, i, 0);   // repair arrow damage
967                     if(lineGap) damage[nr][i][j] = False; // this flushed the square as well
968                 } else damage[nr][i][j] = 1;  // mark for expose
969             }
970
971         /* Second pass -- Draw piece(s) in new position and flash them */
972         for (i = 0; i < BOARD_HEIGHT; i++)
973           for (j = 0; j < BOARD_WIDTH; j++)
974             if (board[i][j] != lastBoard[nr][i][j] || !nr && marker[i][j] != lastMarker[i][j]) {
975                 DrawSquare(i, j, board[i][j], do_flash);
976                 damage[nr][i][j] = 1; // mark for expose
977             }
978     } else {
979         if (lineGap > 0)
980           DrawGrid();
981
982         for (i = 0; i < BOARD_HEIGHT; i++)
983           for (j = 0; j < BOARD_WIDTH; j++) {
984               DrawSquare(i, j, board[i][j], 0);
985               damage[nr][i][j] = False;
986           }
987
988         exposeAll = True;
989     }
990
991     CopyBoard(lastBoard[nr], board);
992     lastBoardValid[nr] = 1;
993   if(nr == 0) { // [HGM] dual: no highlights on second board yet
994     lastFlipView = flipView;
995     for (i = 0; i < BOARD_HEIGHT; i++)
996         for (j = 0; j < BOARD_WIDTH; j++)
997             lastMarker[i][j] = marker[i][j];
998
999     /* Draw highlights */
1000     if (pm1X >= 0 && pm1Y >= 0) {
1001       drawHighlight(pm1X, pm1Y, 2);
1002       if(lineGap) damage[nr][pm1Y][pm1X] = False;
1003     }
1004     if (pm2X >= 0 && pm2Y >= 0) {
1005       drawHighlight(pm2X, pm2Y, 2);
1006       if(lineGap) damage[nr][pm2Y][pm2X] = False;
1007     }
1008     if (hi1X >= 0 && hi1Y >= 0) {
1009       drawHighlight(hi1X, hi1Y, 1);
1010       if(lineGap) damage[nr][hi1Y][hi1X] = False;
1011     }
1012     if (hi2X >= 0 && hi2Y >= 0) {
1013       drawHighlight(hi2X, hi2Y, 1);
1014       if(lineGap) damage[nr][hi2Y][hi2X] = False;
1015     }
1016     DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y);
1017   }
1018   else DrawArrowHighlight (board[EP_STATUS-3], board[EP_STATUS-4], board[EP_STATUS-1], board[EP_STATUS-2]);
1019
1020     /* If piece being dragged around board, must redraw that too */
1021     DrawDragPiece();
1022
1023     if(exposeAll)
1024         GraphExpose(currBoard, 0, 0, BOARD_WIDTH*(squareSize + lineGap) + lineGap, BOARD_HEIGHT*(squareSize + lineGap) + lineGap);
1025     else {
1026         for (i = 0; i < BOARD_HEIGHT; i++)
1027             for (j = 0; j < BOARD_WIDTH; j++)
1028                 if(damage[nr][i][j]) {
1029                     int x, y;
1030                     if (flipView) {
1031                         x = lineGap + ((BOARD_WIDTH-1)-j) *
1032                           (squareSize + lineGap);
1033                         y = lineGap + i * (squareSize + lineGap);
1034                     } else {
1035                         x = lineGap + j * (squareSize + lineGap);
1036                         y = lineGap + ((BOARD_HEIGHT-1)-i) *
1037                           (squareSize + lineGap);
1038                     }
1039                     if(damage[nr][i][j] & 2) // damage by old or new arrow
1040                         GraphExpose(currBoard, x - lineGap, y - lineGap, squareSize + 2*lineGap, squareSize  + 2*lineGap);
1041                     else
1042                         GraphExpose(currBoard, x, y, squareSize, squareSize);
1043                     damage[nr][i][j] &= 2; // remember damage by newly drawn error in '2' bit, to schedule it for erasure next draw
1044                 }
1045     }
1046
1047     FlashDelay(0); // this flushes drawing queue;
1048     if(nr) SwitchWindow(1);
1049 }
1050
1051 /* [AS] Arrow highlighting support */
1052
1053 static double A_WIDTH = 5; /* Width of arrow body */
1054
1055 #define A_HEIGHT_FACTOR 6   /* Length of arrow "point", relative to body width */
1056 #define A_WIDTH_FACTOR  3   /* Width of arrow "point", relative to body width */
1057
1058 static double
1059 Sqr (double x)
1060 {
1061     return x*x;
1062 }
1063
1064 static int
1065 Round (double x)
1066 {
1067     return (int) (x + 0.5);
1068 }
1069
1070 void
1071 SquareToPos (int rank, int file, int *x, int *y)
1072 {
1073     if (flipView) {
1074         *x = lineGap + ((BOARD_WIDTH-1)-file) * (squareSize + lineGap);
1075         *y = lineGap + rank * (squareSize + lineGap);
1076     } else {
1077         *x = lineGap + file * (squareSize + lineGap);
1078         *y = lineGap + ((BOARD_HEIGHT-1)-rank) * (squareSize + lineGap);
1079     }
1080 }
1081
1082 /* Draw an arrow between two points using current settings */
1083 static void
1084 DrawArrowBetweenPoints (int s_x, int s_y, int d_x, int d_y)
1085 {
1086     Pnt arrow[8];
1087     double dx, dy, j, k, x, y;
1088
1089     if( d_x == s_x ) {
1090         int h = (d_y > s_y) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1091
1092         arrow[0].x = s_x + A_WIDTH + 0.5;
1093         arrow[0].y = s_y;
1094
1095         arrow[1].x = s_x + A_WIDTH + 0.5;
1096         arrow[1].y = d_y - h;
1097
1098         arrow[2].x = arrow[1].x + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1099         arrow[2].y = d_y - h;
1100
1101         arrow[3].x = d_x;
1102         arrow[3].y = d_y;
1103
1104         arrow[5].x = arrow[1].x - 2*A_WIDTH + 0.5;
1105         arrow[5].y = d_y - h;
1106
1107         arrow[4].x = arrow[5].x - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1108         arrow[4].y = d_y - h;
1109
1110         arrow[6].x = arrow[1].x - 2*A_WIDTH + 0.5;
1111         arrow[6].y = s_y;
1112     }
1113     else if( d_y == s_y ) {
1114         int w = (d_x > s_x) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1115
1116         arrow[0].x = s_x;
1117         arrow[0].y = s_y + A_WIDTH + 0.5;
1118
1119         arrow[1].x = d_x - w;
1120         arrow[1].y = s_y + A_WIDTH + 0.5;
1121
1122         arrow[2].x = d_x - w;
1123         arrow[2].y = arrow[1].y + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1124
1125         arrow[3].x = d_x;
1126         arrow[3].y = d_y;
1127
1128         arrow[5].x = d_x - w;
1129         arrow[5].y = arrow[1].y - 2*A_WIDTH + 0.5;
1130
1131         arrow[4].x = d_x - w;
1132         arrow[4].y = arrow[5].y - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1133
1134         arrow[6].x = s_x;
1135         arrow[6].y = arrow[1].y - 2*A_WIDTH + 0.5;
1136     }
1137     else {
1138         /* [AS] Needed a lot of paper for this! :-) */
1139         dy = (double) (d_y - s_y) / (double) (d_x - s_x);
1140         dx = (double) (s_x - d_x) / (double) (s_y - d_y);
1141
1142         j = sqrt( Sqr(A_WIDTH) / (1.0 + Sqr(dx)) );
1143
1144         k = sqrt( Sqr(A_WIDTH*A_HEIGHT_FACTOR) / (1.0 + Sqr(dy)) );
1145
1146         x = s_x;
1147         y = s_y;
1148
1149         arrow[0].x = Round(x - j);
1150         arrow[0].y = Round(y + j*dx);
1151
1152         arrow[1].x = Round(arrow[0].x + 2*j);   // [HGM] prevent width to be affected by rounding twice
1153         arrow[1].y = Round(arrow[0].y - 2*j*dx);
1154
1155         if( d_x > s_x ) {
1156             x = (double) d_x - k;
1157             y = (double) d_y - k*dy;
1158         }
1159         else {
1160             x = (double) d_x + k;
1161             y = (double) d_y + k*dy;
1162         }
1163
1164         x = Round(x); y = Round(y); // [HGM] make sure width of shaft is rounded the same way on both ends
1165
1166         arrow[6].x = Round(x - j);
1167         arrow[6].y = Round(y + j*dx);
1168
1169         arrow[2].x = Round(arrow[6].x + 2*j);
1170         arrow[2].y = Round(arrow[6].y - 2*j*dx);
1171
1172         arrow[3].x = Round(arrow[2].x + j*(A_WIDTH_FACTOR-1));
1173         arrow[3].y = Round(arrow[2].y - j*(A_WIDTH_FACTOR-1)*dx);
1174
1175         arrow[4].x = d_x;
1176         arrow[4].y = d_y;
1177
1178         arrow[5].x = Round(arrow[6].x - j*(A_WIDTH_FACTOR-1));
1179         arrow[5].y = Round(arrow[6].y + j*(A_WIDTH_FACTOR-1)*dx);
1180     }
1181
1182     DrawPolygon(arrow, 7);
1183 //    Polygon( hdc, arrow, 7 );
1184 }
1185
1186 static void
1187 ArrowDamage (int s_col, int s_row, int d_col, int d_row)
1188 {
1189     int hor, vert, i, n = partnerUp * twoBoards;
1190     hor = 64*s_col + 32; vert = 64*s_row + 32;
1191     for(i=0; i<= 64; i++) {
1192             damage[n][vert+6>>6][hor+6>>6] |= 2;
1193             damage[n][vert-6>>6][hor+6>>6] |= 2;
1194             damage[n][vert+6>>6][hor-6>>6] |= 2;
1195             damage[n][vert-6>>6][hor-6>>6] |= 2;
1196             hor += d_col - s_col; vert += d_row - s_row;
1197     }
1198 }
1199
1200 /* [AS] Draw an arrow between two squares */
1201 static void
1202 DrawArrowBetweenSquares (int s_col, int s_row, int d_col, int d_row)
1203 {
1204     int s_x, s_y, d_x, d_y;
1205
1206     if( s_col == d_col && s_row == d_row ) {
1207         return;
1208     }
1209
1210     /* Get source and destination points */
1211     SquareToPos( s_row, s_col, &s_x, &s_y);
1212     SquareToPos( d_row, d_col, &d_x, &d_y);
1213
1214     if( d_y > s_y ) {
1215         d_y += squareSize / 2 - squareSize / 4; // [HGM] round towards same centers on all sides!
1216     }
1217     else if( d_y < s_y ) {
1218         d_y += squareSize / 2 + squareSize / 4;
1219     }
1220     else {
1221         d_y += squareSize / 2;
1222     }
1223
1224     if( d_x > s_x ) {
1225         d_x += squareSize / 2 - squareSize / 4;
1226     }
1227     else if( d_x < s_x ) {
1228         d_x += squareSize / 2 + squareSize / 4;
1229     }
1230     else {
1231         d_x += squareSize / 2;
1232     }
1233
1234     s_x += squareSize / 2;
1235     s_y += squareSize / 2;
1236
1237     /* Adjust width */
1238     A_WIDTH = squareSize / 14.; //[HGM] make float
1239
1240     DrawArrowBetweenPoints( s_x, s_y, d_x, d_y );
1241     ArrowDamage(s_col, s_row, d_col, d_row);
1242 }
1243
1244 static Boolean
1245 IsDrawArrowEnabled ()
1246 {
1247     return (appData.highlightMoveWithArrow || twoBoards && partnerUp) && squareSize >= 32;
1248 }
1249
1250 static void
1251 DrawArrowHighlight (int fromX, int fromY, int toX,int toY)
1252 {
1253     if( IsDrawArrowEnabled() && fromX >= 0 && fromY >= 0 && toX >= 0 && toY >= 0)
1254         DrawArrowBetweenSquares(fromX, fromY, toX, toY);
1255 }