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