2 * board.c -- platform-independent drawing code for XBoard
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
10 * The following terms apply to Digital Equipment Corporation's copyright
12 * ------------------------------------------------------------------------
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.
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
30 * ------------------------------------------------------------------------
32 * The following terms apply to the enhanced version of XBoard
33 * distributed by the Free Software Foundation:
34 * ------------------------------------------------------------------------
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.
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.
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/. *
49 *------------------------------------------------------------------------
50 ** See the file ChangeLog for a revision history. */
60 #include <sys/types.h>
68 #else /* not STDC_HEADERS */
69 extern char *getenv();
72 # else /* not HAVE_STRING_H */
74 # endif /* not HAVE_STRING_H */
75 #endif /* not STDC_HEADERS */
77 #if TIME_WITH_SYS_TIME
78 # include <sys/time.h>
82 # include <sys/time.h>
93 # include <sys/wait.h>
107 #define usleep(t) _sleep2(((t)+500)/1000)
111 int squareSize, lineGap;
113 int damage[2][BOARD_RANKS][BOARD_FILES];
115 /* There can be two pieces being animated at once: a player
116 can begin dragging a piece before the remote opponent has moved. */
118 AnimState anims[NrOfAnims];
120 static void DrawSquare P((int row, int column, ChessSquare piece, int do_flash));
121 static Boolean IsDrawArrowEnabled P((void));
122 static void DrawArrowHighlight P((int fromX, int fromY, int toX,int toY));
123 static void ArrowDamage P((int s_col, int s_row, int d_col, int d_row));
126 drawHighlight (int file, int rank, int type)
130 if (lineGap == 0) return;
133 x = lineGap/2 + ((BOARD_WIDTH-1)-file) *
134 (squareSize + lineGap);
135 y = lineGap/2 + rank * (squareSize + lineGap);
137 x = lineGap/2 + file * (squareSize + lineGap);
138 y = lineGap/2 + ((BOARD_HEIGHT-1)-rank) *
139 (squareSize + lineGap);
142 DrawBorder(x,y, type);
145 int hi1X = -1, hi1Y = -1, hi2X = -1, hi2Y = -1;
146 int pm1X = -1, pm1Y = -1, pm2X = -1, pm2Y = -1;
149 SetHighlights (int fromX, int fromY, int toX, int toY)
151 int arrow = hi2X >= 0 && hi1Y >= 0 && IsDrawArrowEnabled();
153 if (hi1X != fromX || hi1Y != fromY) {
154 if (hi1X >= 0 && hi1Y >= 0) {
155 drawHighlight(hi1X, hi1Y, 0);
157 } // [HGM] first erase both, then draw new!
159 if (hi2X != toX || hi2Y != toY) {
160 if (hi2X >= 0 && hi2Y >= 0) {
161 drawHighlight(hi2X, hi2Y, 0);
165 if(arrow) // there currently is an arrow displayed
166 ArrowDamage(hi1X, hi1Y, hi2X, hi2Y); // mark which squares it damaged
168 if (hi1X != fromX || hi1Y != fromY) {
169 if (fromX >= 0 && fromY >= 0) {
170 drawHighlight(fromX, fromY, 1);
173 if (hi2X != toX || hi2Y != toY) {
174 if (toX >= 0 && toY >= 0) {
175 drawHighlight(toX, toY, 1);
184 if(arrow || toX >= 0 && fromY >= 0 && IsDrawArrowEnabled())
185 DrawPosition(FALSE, NULL); // repair any arrow damage, or draw a new one
191 SetHighlights(-1, -1, -1, -1);
196 SetPremoveHighlights (int fromX, int fromY, int toX, int toY)
198 if (pm1X != fromX || pm1Y != fromY) {
199 if (pm1X >= 0 && pm1Y >= 0) {
200 drawHighlight(pm1X, pm1Y, 0);
202 if (fromX >= 0 && fromY >= 0) {
203 drawHighlight(fromX, fromY, 2);
206 if (pm2X != toX || pm2Y != toY) {
207 if (pm2X >= 0 && pm2Y >= 0) {
208 drawHighlight(pm2X, pm2Y, 0);
210 if (toX >= 0 && toY >= 0) {
211 drawHighlight(toX, toY, 2);
221 ClearPremoveHighlights ()
223 SetPremoveHighlights(-1, -1, -1, -1);
227 * If the user selects on a border boundary, return -1; if off the board,
228 * return -2. Otherwise map the event coordinate to the square.
231 EventToSquare (int x, int limit)
238 if ((x % (squareSize + lineGap)) >= squareSize)
240 x /= (squareSize + lineGap);
246 /* [HR] determine square color depending on chess variant. */
248 SquareColor (int row, int column)
252 if (gameInfo.variant == VariantXiangqi) {
253 if (column >= 3 && column <= 5 && row >= 0 && row <= 2) {
255 } else if (column >= 3 && column <= 5 && row >= 7 && row <= 9) {
257 } else if (row <= 4) {
263 square_color = ((column + row) % 2) == 1;
266 /* [hgm] holdings: next line makes all holdings squares light */
267 if(column < BOARD_LEFT || column >= BOARD_RGHT) square_color = 1;
269 if ( // [HGM] holdings: blank out area between board and holdings
270 column == BOARD_LEFT-1
271 || column == BOARD_RGHT
272 || (column == BOARD_LEFT-2 && row < BOARD_HEIGHT-gameInfo.holdingsSize)
273 || (column == BOARD_RGHT+1 && row >= gameInfo.holdingsSize) )
274 square_color = 2; // black
279 /* Convert board position to corner of screen rect and color */
282 ScreenSquare (int column, int row, Pnt *pt, int *color)
285 pt->x = lineGap + ((BOARD_WIDTH-1)-column) * (squareSize + lineGap);
286 pt->y = lineGap + row * (squareSize + lineGap);
288 pt->x = lineGap + column * (squareSize + lineGap);
289 pt->y = lineGap + ((BOARD_HEIGHT-1)-row) * (squareSize + lineGap);
291 *color = SquareColor(row, column);
294 /* Convert window coords to square */
297 BoardSquare (int x, int y, int *column, int *row)
299 *column = EventToSquare(x, BOARD_WIDTH);
300 if (flipView && *column >= 0)
301 *column = BOARD_WIDTH - 1 - *column;
302 *row = EventToSquare(y, BOARD_HEIGHT);
303 if (!flipView && *row >= 0)
304 *row = BOARD_HEIGHT - 1 - *row;
307 /* Generate a series of frame coords from start->mid->finish.
308 The movement rate doubles until the half way point is
309 reached, then halves back down to the final destination,
310 which gives a nice slow in/out effect. The algorithmn
311 may seem to generate too many intermediates for short
312 moves, but remember that the purpose is to attract the
313 viewers attention to the piece about to be moved and
314 then to where it ends up. Too few frames would be less
318 Tween (Pnt *start, Pnt *mid, Pnt *finish, int factor, Pnt frames[], int *nFrames)
320 int fraction, n, count;
324 /* Slow in, stepping 1/16th, then 1/8th, ... */
326 for (n = 0; n < factor; n++)
328 for (n = 0; n < factor; n++) {
329 frames[count].x = start->x + (mid->x - start->x) / fraction;
330 frames[count].y = start->y + (mid->y - start->y) / fraction;
332 fraction = fraction / 2;
336 frames[count] = *mid;
339 /* Slow out, stepping 1/2, then 1/4, ... */
341 for (n = 0; n < factor; n++) {
342 frames[count].x = finish->x - (finish->x - mid->x) / fraction;
343 frames[count].y = finish->y - (finish->y - mid->y) / fraction;
345 fraction = fraction * 2;
350 /**** Animation code by Hugh Fisher, DCS, ANU.
352 Known problem: if a window overlapping the board is
353 moved away while a piece is being animated underneath,
354 the newly exposed area won't be updated properly.
355 I can live with this.
357 Known problem: if you look carefully at the animation
358 of pieces in mono mode, they are being drawn as solid
359 shapes without interior detail while moving. Fixing
360 this would be a major complication for minimal return.
365 #undef Max /* just in case */
367 #define Max(a, b) ((a) > (b) ? (a) : (b))
368 #define Min(a, b) ((a) < (b) ? (a) : (b))
371 short int x, y, width, height;
381 SetRect (MyRectangle *rect, int x, int y, int width, int height)
386 rect->height = height;
389 /* Test if two frames overlap. If they do, return
390 intersection rect within old and location of
391 that rect within new. */
394 Intersect ( Pnt *old, Pnt *new, int size, MyRectangle *area, Pnt *pt)
396 if (old->x > new->x + size || new->x > old->x + size ||
397 old->y > new->y + size || new->y > old->y + size) {
400 SetRect(area, Max(new->x - old->x, 0), Max(new->y - old->y, 0),
401 size - abs(old->x - new->x), size - abs(old->y - new->y));
402 pt->x = Max(old->x - new->x, 0);
403 pt->y = Max(old->y - new->y, 0);
408 /* For two overlapping frames, return the rect(s)
409 in the old that do not intersect with the new. */
412 CalcUpdateRects (Pnt *old, Pnt *new, int size, MyRectangle update[], int *nUpdates)
416 /* If old = new (shouldn't happen) then nothing to draw */
417 if (old->x == new->x && old->y == new->y) {
421 /* Work out what bits overlap. Since we know the rects
422 are the same size we don't need a full intersect calc. */
424 /* Top or bottom edge? */
425 if (new->y > old->y) {
426 SetRect(&(update[count]), old->x, old->y, size, new->y - old->y);
428 } else if (old->y > new->y) {
429 SetRect(&(update[count]), old->x, old->y + size - (old->y - new->y),
430 size, old->y - new->y);
433 /* Left or right edge - don't overlap any update calculated above. */
434 if (new->x > old->x) {
435 SetRect(&(update[count]), old->x, Max(new->y, old->y),
436 new->x - old->x, size - abs(new->y - old->y));
438 } else if (old->x > new->x) {
439 SetRect(&(update[count]), new->x + size, Max(new->y, old->y),
440 old->x - new->x, size - abs(new->y - old->y));
447 /* Animate the movement of a single piece */
450 BeginAnimation (AnimNr anr, ChessSquare piece, ChessSquare bgPiece, int startColor, Pnt *start)
452 AnimState *anim = &anims[anr];
454 if(appData.upsideDown && flipView) piece += piece < BlackPawn ? BlackPawn : -BlackPawn;
455 /* The old buffer is initialised with the start square (empty) */
456 if(bgPiece == EmptySquare) {
457 DrawBlank(anr, start->x, start->y, startColor);
459 /* Kludge alert: When gating we want the introduced
460 piece to appear on the from square. To generate an
461 image of it, we draw it on the board, copy the image,
462 and draw the original piece again. */
463 if(piece != bgPiece) DrawSquare(anim->startBoardY, anim->startBoardX, bgPiece, 0);
464 CopyRectangle(anr, DISP, 2,
465 start->x, start->y, squareSize, squareSize,
466 0, 0); // [HGM] zh: unstack in stead of grab
467 if(piece != bgPiece) DrawSquare(anim->startBoardY, anim->startBoardX, piece, 0);
469 anim->prevFrame = *start;
471 SetDragPiece(anr, piece);
475 AnimationFrame (AnimNr anr, Pnt *frame, ChessSquare piece)
477 MyRectangle updates[4];
481 AnimState *anim = &anims[anr];
483 /* Save what we are about to draw into the new buffer */
484 CopyRectangle(anr, DISP, 0,
485 frame->x, frame->y, squareSize, squareSize,
488 /* Erase bits of the previous frame */
489 if (Intersect(&anim->prevFrame, frame, squareSize, &overlap, &pt)) {
490 /* Where the new frame overlapped the previous,
491 the contents in newBuf are wrong. */
492 CopyRectangle(anr, 2, 0,
493 overlap.x, overlap.y,
494 overlap.width, overlap.height,
496 /* Repaint the areas in the old that don't overlap new */
497 CalcUpdateRects(&anim->prevFrame, frame, squareSize, updates, &count);
498 for (i = 0; i < count; i++)
499 CopyRectangle(anr, 2, DISP,
500 updates[i].x - anim->prevFrame.x,
501 updates[i].y - anim->prevFrame.y,
502 updates[i].width, updates[i].height,
503 updates[i].x, updates[i].y);
505 /* Easy when no overlap */
506 CopyRectangle(anr, 2, DISP,
507 0, 0, squareSize, squareSize,
508 anim->prevFrame.x, anim->prevFrame.y);
511 /* Save this frame for next time round */
512 CopyRectangle(anr, 0, 2,
513 0, 0, squareSize, squareSize,
515 anim->prevFrame = *frame;
517 /* Draw piece over original screen contents, not current,
518 and copy entire rect. Wipes out overlapping piece images. */
519 InsertPiece(anr, piece);
520 CopyRectangle(anr, 0, DISP,
521 0, 0, squareSize, squareSize,
526 EndAnimation (AnimNr anr, Pnt *finish)
528 MyRectangle updates[4];
532 AnimState *anim = &anims[anr];
534 /* The main code will redraw the final square, so we
535 only need to erase the bits that don't overlap. */
536 if (Intersect(&anim->prevFrame, finish, squareSize, &overlap, &pt)) {
537 CalcUpdateRects(&anim->prevFrame, finish, squareSize, updates, &count);
538 for (i = 0; i < count; i++)
539 CopyRectangle(anr, 2, DISP,
540 updates[i].x - anim->prevFrame.x,
541 updates[i].y - anim->prevFrame.y,
542 updates[i].width, updates[i].height,
543 updates[i].x, updates[i].y);
545 CopyRectangle(anr, 2, DISP,
546 0, 0, squareSize, squareSize,
547 anim->prevFrame.x, anim->prevFrame.y);
552 FrameSequence (AnimNr anr, ChessSquare piece, int startColor, Pnt *start, Pnt *finish, Pnt frames[], int nFrames)
556 BeginAnimation(anr, piece, EmptySquare, startColor, start);
557 for (n = 0; n < nFrames; n++) {
558 AnimationFrame(anr, &(frames[n]), piece);
559 FrameDelay(appData.animSpeed);
561 EndAnimation(anr, finish);
565 AnimateAtomicCapture (Board board, int fromX, int fromY, int toX, int toY)
568 ChessSquare piece = board[fromY][toY];
569 board[fromY][toY] = EmptySquare;
570 DrawPosition(FALSE, board);
572 x = lineGap + ((BOARD_WIDTH-1)-toX) * (squareSize + lineGap);
573 y = lineGap + toY * (squareSize + lineGap);
575 x = lineGap + toX * (squareSize + lineGap);
576 y = lineGap + ((BOARD_HEIGHT-1)-toY) * (squareSize + lineGap);
578 for(i=1; i<4*kFactor; i++) {
579 int r = squareSize * 9 * i/(20*kFactor - 5);
580 DrawDot(1, x + squareSize/2 - r, y+squareSize/2 - r, 2*r);
581 FrameDelay(appData.animSpeed);
583 board[fromY][toY] = piece;
586 /* Main control logic for deciding what to animate and how */
589 AnimateMove (Board board, int fromX, int fromY, int toX, int toY)
593 Pnt start, finish, mid;
594 Pnt frames[kFactor * 2 + 1];
595 int nFrames, startColor, endColor;
597 /* Are we animating? */
598 if (!appData.animate || appData.blindfold)
601 if(board[toY][toX] == WhiteRook && board[fromY][fromX] == WhiteKing ||
602 board[toY][toX] == BlackRook && board[fromY][fromX] == BlackKing)
603 return; // [HGM] FRC: no animtion of FRC castlings, as to-square is not true to-square
605 if (fromY < 0 || fromX < 0 || toX < 0 || toY < 0) return;
606 piece = board[fromY][fromX];
607 if (piece >= EmptySquare) return;
612 hop = abs(fromX-toX) == 1 && abs(fromY-toY) == 2 || abs(fromX-toX) == 2 && abs(fromY-toY) == 1;
615 ScreenSquare(fromX, fromY, &start, &startColor);
616 ScreenSquare(toX, toY, &finish, &endColor);
619 /* Knight: make straight movement then diagonal */
620 if (abs(toY - fromY) < abs(toX - fromX)) {
621 mid.x = start.x + (finish.x - start.x) / 2;
625 mid.y = start.y + (finish.y - start.y) / 2;
628 mid.x = start.x + (finish.x - start.x) / 2;
629 mid.y = start.y + (finish.y - start.y) / 2;
632 /* Don't use as many frames for very short moves */
633 if (abs(toY - fromY) + abs(toX - fromX) <= 2)
634 Tween(&start, &mid, &finish, kFactor - 1, frames, &nFrames);
636 Tween(&start, &mid, &finish, kFactor, frames, &nFrames);
637 FrameSequence(Game, piece, startColor, &start, &finish, frames, nFrames);
638 if(Explode(board, fromX, fromY, toX, toY)) { // mark as damaged
640 for(i=0; i<BOARD_WIDTH; i++) for(j=0; j<BOARD_HEIGHT; j++)
641 if((i-toX)*(i-toX) + (j-toY)*(j-toY) < 6) damage[0][j][i] = True;
644 /* Be sure end square is redrawn */
645 damage[0][toY][toX] = True;
649 ChangeDragPiece (ChessSquare piece)
651 anims[Player].dragPiece = piece;
652 SetDragPiece(Player, piece);
656 DragPieceMove (int x, int y)
660 /* Are we animating? */
661 if (!appData.animateDragging || appData.blindfold)
665 if (! anims[Player].dragActive)
667 /* Move piece, maintaining same relative position
668 of mouse within square */
669 corner.x = x - anims[Player].mouseDelta.x;
670 corner.y = y - anims[Player].mouseDelta.y;
671 AnimationFrame(Player, &corner, anims[Player].dragPiece);
673 if (appData.highlightDragging) {
675 BoardSquare(x, y, &boardX, &boardY);
676 SetHighlights(fromX, fromY, boardX, boardY);
682 DragPieceEnd (int x, int y)
684 int boardX, boardY, color;
687 /* Are we animating? */
688 if (!appData.animateDragging || appData.blindfold)
692 if (! anims[Player].dragActive)
694 /* Last frame in sequence is square piece is
695 placed on, which may not match mouse exactly. */
696 BoardSquare(x, y, &boardX, &boardY);
697 ScreenSquare(boardX, boardY, &corner, &color);
698 EndAnimation(Player, &corner);
700 /* Be sure end square is redrawn */
701 damage[0][boardY][boardX] = True;
703 /* This prevents weird things happening with fast successive
704 clicks which on my Sun at least can cause motion events
705 without corresponding press/release. */
706 anims[Player].dragActive = False;
710 DragPieceBegin (int x, int y, Boolean instantly)
712 int boardX, boardY, color;
715 /* Are we animating? */
716 if (!appData.animateDragging || appData.blindfold)
719 /* Figure out which square we start in and the
720 mouse position relative to top left corner. */
721 BoardSquare(x, y, &boardX, &boardY);
722 anims[Player].startBoardX = boardX;
723 anims[Player].startBoardY = boardY;
724 ScreenSquare(boardX, boardY, &corner, &color);
725 anims[Player].startSquare = corner;
726 anims[Player].startColor = color;
727 /* As soon as we start dragging, the piece will jump slightly to
728 be centered over the mouse pointer. */
729 anims[Player].mouseDelta.x = squareSize/2;
730 anims[Player].mouseDelta.y = squareSize/2;
731 /* Initialise animation */
732 anims[Player].dragPiece = PieceForSquare(boardX, boardY);
734 if (anims[Player].dragPiece >= 0 && anims[Player].dragPiece < EmptySquare) {
735 ChessSquare bgPiece = EmptySquare;
736 anims[Player].dragActive = True;
737 if(boardX == BOARD_RGHT+1 && PieceForSquare(boardX-1, boardY) > 1 ||
738 boardX == BOARD_LEFT-2 && PieceForSquare(boardX+1, boardY) > 1)
739 bgPiece = anims[Player].dragPiece;
740 if(gatingPiece != EmptySquare) bgPiece = gatingPiece;
741 BeginAnimation(Player, anims[Player].dragPiece, bgPiece, color, &corner);
742 /* Mark this square as needing to be redrawn. Note that
743 we don't remove the piece though, since logically (ie
744 as seen by opponent) the move hasn't been made yet. */
745 damage[0][boardY][boardX] = True;
747 anims[Player].dragActive = False;
751 /* Handle expose event while piece being dragged */
756 if (!anims[Player].dragActive || appData.blindfold)
759 /* What we're doing: logically, the move hasn't been made yet,
760 so the piece is still in it's original square. But visually
761 it's being dragged around the board. So we erase the square
762 that the piece is on and draw it at the last known drag point. */
763 DrawOneSquare(anims[Player].startSquare.x, anims[Player].startSquare.y,
764 EmptySquare, anims[Player].startColor, 0, NULL, 0);
765 AnimationFrame(Player, &anims[Player].prevFrame, anims[Player].dragPiece);
766 damage[0][anims[Player].startBoardY][anims[Player].startBoardX] = TRUE;
770 DrawSquare (int row, int column, ChessSquare piece, int do_flash)
772 int square_color, x, y, align=0;
777 /* Calculate delay in milliseconds (2-delays per complete flash) */
778 flash_delay = 500 / appData.flashRate;
781 x = lineGap + ((BOARD_WIDTH-1)-column) *
782 (squareSize + lineGap);
783 y = lineGap + row * (squareSize + lineGap);
785 x = lineGap + column * (squareSize + lineGap);
786 y = lineGap + ((BOARD_HEIGHT-1)-row) *
787 (squareSize + lineGap);
790 square_color = SquareColor(row, column);
792 string[1] = NULLCHAR;
793 if (appData.showCoords && row == (flipView ? BOARD_HEIGHT-1 : 0)
794 && column >= BOARD_LEFT && column < BOARD_RGHT) {
795 string[0] = 'a' + column - BOARD_LEFT;
796 align = 1; // coord in lower-right corner
798 if (appData.showCoords && column == (flipView ? BOARD_RGHT-1 : BOARD_LEFT)) {
799 string[0] = ONE + row;
800 align = 2; // coord in upper-left corner
802 if (column == (flipView ? BOARD_LEFT-1 : BOARD_RGHT) && piece > 1 ) {
803 string[0] = '0' + piece;
804 align = 3; // holdings count in upper-right corner
806 if (column == (flipView ? BOARD_RGHT : BOARD_LEFT-1) && piece > 1) {
807 string[0] = '0' + piece;
808 align = 4; // holdings count in upper-left corner
810 if(square_color == 2 || appData.blindfold) piece = EmptySquare;
812 if (do_flash && piece != EmptySquare && appData.flashCount > 0) {
813 for (i=0; i<appData.flashCount; ++i) {
814 DrawOneSquare(x, y, piece, square_color, 0, string, 0);
815 FlashDelay(flash_delay);
816 DrawOneSquare(x, y, EmptySquare, square_color, 0, string, 0);
817 FlashDelay(flash_delay);
820 DrawOneSquare(x, y, piece, square_color, partnerUp ? 0 : marker[row][column], string, align);
823 /* Returns 1 if there are "too many" differences between b1 and b2
824 (i.e. more than 1 move was made) */
826 too_many_diffs (Board b1, Board b2)
831 for (i=0; i<BOARD_HEIGHT; ++i) {
832 for (j=0; j<BOARD_WIDTH; ++j) {
833 if (b1[i][j] != b2[i][j]) {
834 if (++c > 4) /* Castling causes 4 diffs */
842 /* Matrix describing castling maneuvers */
843 /* Row, ColRookFrom, ColKingFrom, ColRookTo, ColKingTo */
844 static int castling_matrix[4][5] = {
845 { 0, 0, 4, 3, 2 }, /* 0-0-0, white */
846 { 0, 7, 4, 5, 6 }, /* 0-0, white */
847 { 7, 0, 4, 3, 2 }, /* 0-0-0, black */
848 { 7, 7, 4, 5, 6 } /* 0-0, black */
851 /* Checks whether castling occurred. If it did, *rrow and *rcol
852 are set to the destination (row,col) of the rook that moved.
854 Returns 1 if castling occurred, 0 if not.
856 Note: Only handles a max of 1 castling move, so be sure
857 to call too_many_diffs() first.
860 check_castle_draw (Board newb, Board oldb, int *rrow, int *rcol)
865 /* For each type of castling... */
866 for (i=0; i<4; ++i) {
867 r = castling_matrix[i];
869 /* Check the 4 squares involved in the castling move */
871 for (j=1; j<=4; ++j) {
872 if (newb[r[0]][r[j]] == oldb[r[0]][r[j]]) {
879 /* All 4 changed, so it must be a castling move */
889 DrawPosition (int repaint, Board board)
892 static int lastFlipView = 0;
893 static int lastBoardValid[2] = {0, 0};
894 static Board lastBoard[2];
895 static char lastMarker[BOARD_RANKS][BOARD_FILES];
897 int nr = twoBoards*partnerUp;
899 if(DrawSeekGraph()) return; // [HGM] seekgraph: suppress any drawing if seek graph up
902 if (!lastBoardValid[nr]) return;
903 board = lastBoard[nr];
905 if (!lastBoardValid[nr] || (nr == 0 && lastFlipView != flipView)) {
906 MarkMenuItem("View.Flip View", flipView);
909 if(nr) { SlavePopUp(); SwitchWindow(); } // [HGM] popup board if not yet popped up, and switch drawing to it.
912 * It would be simpler to clear the window with XClearWindow()
913 * but this causes a very distracting flicker.
916 if (!repaint && lastBoardValid[nr] && (nr == 1 || lastFlipView == flipView)) {
918 if ( lineGap && IsDrawArrowEnabled())
921 /* If too much changes (begin observing new game, etc.), don't
923 do_flash = too_many_diffs(board, lastBoard[nr]) ? 0 : 1;
925 /* Special check for castling so we don't flash both the king
926 and the rook (just flash the king). */
928 if (check_castle_draw(board, lastBoard[nr], &rrow, &rcol)) {
929 /* Draw rook with NO flashing. King will be drawn flashing later */
930 DrawSquare(rrow, rcol, board[rrow][rcol], 0);
931 lastBoard[nr][rrow][rcol] = board[rrow][rcol];
935 /* First pass -- Draw (newly) empty squares and repair damage.
936 This prevents you from having a piece show up twice while it
937 is flashing on its new square */
938 for (i = 0; i < BOARD_HEIGHT; i++)
939 for (j = 0; j < BOARD_WIDTH; j++)
940 if (((board[i][j] != lastBoard[nr][i][j] || !nr && marker[i][j] != lastMarker[i][j]) && board[i][j] == EmptySquare)
941 || damage[nr][i][j]) {
942 DrawSquare(i, j, board[i][j], 0);
943 damage[nr][i][j] = False;
946 /* Second pass -- Draw piece(s) in new position and flash them */
947 for (i = 0; i < BOARD_HEIGHT; i++)
948 for (j = 0; j < BOARD_WIDTH; j++)
949 if (board[i][j] != lastBoard[nr][i][j] || !nr && marker[i][j] != lastMarker[i][j]) {
950 DrawSquare(i, j, board[i][j], do_flash);
956 for (i = 0; i < BOARD_HEIGHT; i++)
957 for (j = 0; j < BOARD_WIDTH; j++) {
958 DrawSquare(i, j, board[i][j], 0);
959 damage[nr][i][j] = False;
963 CopyBoard(lastBoard[nr], board);
964 lastBoardValid[nr] = 1;
965 if(nr == 0) { // [HGM] dual: no highlights on second board yet
966 lastFlipView = flipView;
967 for (i = 0; i < BOARD_HEIGHT; i++)
968 for (j = 0; j < BOARD_WIDTH; j++)
969 lastMarker[i][j] = marker[i][j];
971 /* Draw highlights */
972 if (pm1X >= 0 && pm1Y >= 0) {
973 drawHighlight(pm1X, pm1Y, 2);
975 if (pm2X >= 0 && pm2Y >= 0) {
976 drawHighlight(pm2X, pm2Y, 2);
978 if (hi1X >= 0 && hi1Y >= 0) {
979 drawHighlight(hi1X, hi1Y, 1);
981 if (hi2X >= 0 && hi2Y >= 0) {
982 drawHighlight(hi2X, hi2Y, 1);
984 DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y);
986 else DrawArrowHighlight (board[EP_STATUS-3], board[EP_STATUS-4], board[EP_STATUS-1], board[EP_STATUS-2]);
988 /* If piece being dragged around board, must redraw that too */
991 FlashDelay(0); // this flushes drawing queue;
992 if(nr) SwitchWindow();
995 /* [AS] Arrow highlighting support */
997 static double A_WIDTH = 5; /* Width of arrow body */
999 #define A_HEIGHT_FACTOR 6 /* Length of arrow "point", relative to body width */
1000 #define A_WIDTH_FACTOR 3 /* Width of arrow "point", relative to body width */
1011 return (int) (x + 0.5);
1015 SquareToPos (int rank, int file, int *x, int *y)
1018 *x = lineGap + ((BOARD_WIDTH-1)-file) * (squareSize + lineGap);
1019 *y = lineGap + rank * (squareSize + lineGap);
1021 *x = lineGap + file * (squareSize + lineGap);
1022 *y = lineGap + ((BOARD_HEIGHT-1)-rank) * (squareSize + lineGap);
1026 /* Draw an arrow between two points using current settings */
1028 DrawArrowBetweenPoints (int s_x, int s_y, int d_x, int d_y)
1031 double dx, dy, j, k, x, y;
1034 int h = (d_y > s_y) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1036 arrow[0].x = s_x + A_WIDTH + 0.5;
1039 arrow[1].x = s_x + A_WIDTH + 0.5;
1040 arrow[1].y = d_y - h;
1042 arrow[2].x = arrow[1].x + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1043 arrow[2].y = d_y - h;
1048 arrow[5].x = arrow[1].x - 2*A_WIDTH + 0.5;
1049 arrow[5].y = d_y - h;
1051 arrow[4].x = arrow[5].x - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1052 arrow[4].y = d_y - h;
1054 arrow[6].x = arrow[1].x - 2*A_WIDTH + 0.5;
1057 else if( d_y == s_y ) {
1058 int w = (d_x > s_x) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1061 arrow[0].y = s_y + A_WIDTH + 0.5;
1063 arrow[1].x = d_x - w;
1064 arrow[1].y = s_y + A_WIDTH + 0.5;
1066 arrow[2].x = d_x - w;
1067 arrow[2].y = arrow[1].y + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1072 arrow[5].x = d_x - w;
1073 arrow[5].y = arrow[1].y - 2*A_WIDTH + 0.5;
1075 arrow[4].x = d_x - w;
1076 arrow[4].y = arrow[5].y - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1079 arrow[6].y = arrow[1].y - 2*A_WIDTH + 0.5;
1082 /* [AS] Needed a lot of paper for this! :-) */
1083 dy = (double) (d_y - s_y) / (double) (d_x - s_x);
1084 dx = (double) (s_x - d_x) / (double) (s_y - d_y);
1086 j = sqrt( Sqr(A_WIDTH) / (1.0 + Sqr(dx)) );
1088 k = sqrt( Sqr(A_WIDTH*A_HEIGHT_FACTOR) / (1.0 + Sqr(dy)) );
1093 arrow[0].x = Round(x - j);
1094 arrow[0].y = Round(y + j*dx);
1096 arrow[1].x = Round(arrow[0].x + 2*j); // [HGM] prevent width to be affected by rounding twice
1097 arrow[1].y = Round(arrow[0].y - 2*j*dx);
1100 x = (double) d_x - k;
1101 y = (double) d_y - k*dy;
1104 x = (double) d_x + k;
1105 y = (double) d_y + k*dy;
1108 x = Round(x); y = Round(y); // [HGM] make sure width of shaft is rounded the same way on both ends
1110 arrow[6].x = Round(x - j);
1111 arrow[6].y = Round(y + j*dx);
1113 arrow[2].x = Round(arrow[6].x + 2*j);
1114 arrow[2].y = Round(arrow[6].y - 2*j*dx);
1116 arrow[3].x = Round(arrow[2].x + j*(A_WIDTH_FACTOR-1));
1117 arrow[3].y = Round(arrow[2].y - j*(A_WIDTH_FACTOR-1)*dx);
1122 arrow[5].x = Round(arrow[6].x - j*(A_WIDTH_FACTOR-1));
1123 arrow[5].y = Round(arrow[6].y + j*(A_WIDTH_FACTOR-1)*dx);
1126 DrawPolygon(arrow, 7);
1127 // Polygon( hdc, arrow, 7 );
1131 ArrowDamage (int s_col, int s_row, int d_col, int d_row)
1133 int hor, vert, i, n = partnerUp * twoBoards;
1134 hor = 64*s_col + 32; vert = 64*s_row + 32;
1135 for(i=0; i<= 64; i++) {
1136 damage[n][vert+6>>6][hor+6>>6] = True;
1137 damage[n][vert-6>>6][hor+6>>6] = True;
1138 damage[n][vert+6>>6][hor-6>>6] = True;
1139 damage[n][vert-6>>6][hor-6>>6] = True;
1140 hor += d_col - s_col; vert += d_row - s_row;
1144 /* [AS] Draw an arrow between two squares */
1146 DrawArrowBetweenSquares (int s_col, int s_row, int d_col, int d_row)
1148 int s_x, s_y, d_x, d_y;
1150 if( s_col == d_col && s_row == d_row ) {
1154 /* Get source and destination points */
1155 SquareToPos( s_row, s_col, &s_x, &s_y);
1156 SquareToPos( d_row, d_col, &d_x, &d_y);
1159 d_y += squareSize / 2 - squareSize / 4; // [HGM] round towards same centers on all sides!
1161 else if( d_y < s_y ) {
1162 d_y += squareSize / 2 + squareSize / 4;
1165 d_y += squareSize / 2;
1169 d_x += squareSize / 2 - squareSize / 4;
1171 else if( d_x < s_x ) {
1172 d_x += squareSize / 2 + squareSize / 4;
1175 d_x += squareSize / 2;
1178 s_x += squareSize / 2;
1179 s_y += squareSize / 2;
1182 A_WIDTH = squareSize / 14.; //[HGM] make float
1184 DrawArrowBetweenPoints( s_x, s_y, d_x, d_y );
1185 ArrowDamage(s_col, s_row, d_col, d_row);
1189 IsDrawArrowEnabled ()
1191 return (appData.highlightMoveWithArrow || twoBoards && partnerUp) && squareSize >= 32;
1195 DrawArrowHighlight (int fromX, int fromY, int toX,int toY)
1197 if( IsDrawArrowEnabled() && fromX >= 0 && fromY >= 0 && toX >= 0 && toY >= 0)
1198 DrawArrowBetweenSquares(fromX, fromY, toX, toY);