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, 2015 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
drawHighlight(int file,int rank,int type)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
SetHighlights(int fromX,int fromY,int toX,int toY)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
ClearHighlights()191 ClearHighlights ()
192 {
193 SetHighlights(-1, -1, -1, -1);
194 }
195
196
197 void
SetPremoveHighlights(int fromX,int fromY,int toX,int toY)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
ClearPremoveHighlights()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
EventToSquare(int x,int limit)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
SquareColor(int row,int column)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
ScreenSquare(int column,int row,Pnt * pt,int * color)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
BoardSquare(int x,int y,int * column,int * row)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
Tween(Pnt * start,Pnt * mid,Pnt * finish,int factor,Pnt frames[],int * nFrames)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
DoSleep(int n)377 DoSleep (int n)
378 {
379 FrameDelay(n);
380 }
381
382 static void
SetRect(MyRectangle * rect,int x,int y,int width,int height)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
Intersect(Pnt * old,Pnt * new,int size,MyRectangle * area,Pnt * pt)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
CalcUpdateRects(Pnt * old,Pnt * new,int size,MyRectangle update[],int * nUpdates)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
BeginAnimation(AnimNr anr,ChessSquare piece,ChessSquare bgPiece,int startColor,Pnt * start)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
AnimationFrame(AnimNr anr,Pnt * frame,ChessSquare piece)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
EndAnimation(AnimNr anr,Pnt * finish)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
FrameSequence(AnimNr anr,ChessSquare piece,int startColor,Pnt * start,Pnt * finish,Pnt frames[],int nFrames)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
AnimateAtomicCapture(Board board,int fromX,int fromY,int toX,int toY)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
AnimateMove(Board board,int fromX,int fromY,int toX,int toY)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
ChangeDragPiece(ChessSquare piece)669 ChangeDragPiece (ChessSquare piece)
670 {
671 anims[Player].dragPiece = piece;
672 SetDragPiece(Player, piece);
673 }
674
675 void
DragPieceMove(int x,int y)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
DragPieceEnd(int x,int y)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
DragPieceBegin(int x,int y,Boolean instantly)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
DrawDragPiece()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
DrawSquare(int row,int column,ChessSquare piece,int do_flash)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
too_many_diffs(Board b1,Board b2)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
check_castle_draw(Board newb,Board oldb,int * rrow,int * rcol)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
DrawPosition(int repaint,Board board)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
Sqr(double x)1059 Sqr (double x)
1060 {
1061 return x*x;
1062 }
1063
1064 static int
Round(double x)1065 Round (double x)
1066 {
1067 return (int) (x + 0.5);
1068 }
1069
1070 void
SquareToPos(int rank,int file,int * x,int * y)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
DrawArrowBetweenPoints(int s_x,int s_y,int d_x,int d_y)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
ArrowDamage(int s_col,int s_row,int d_col,int d_row)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
DrawArrowBetweenSquares(int s_col,int s_row,int d_col,int d_row)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
IsDrawArrowEnabled()1245 IsDrawArrowEnabled ()
1246 {
1247 return (appData.highlightMoveWithArrow || twoBoards && partnerUp) && squareSize >= 32;
1248 }
1249
1250 static void
DrawArrowHighlight(int fromX,int fromY,int toX,int toY)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 }
1256