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