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, hOffset;
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));
123 drawHighlight (int file, int rank, int type)
127 if (lineGap == 0) return;
130 x = lineGap/2 + ((BOARD_WIDTH-1)-file) *
131 (squareSize + lineGap);
132 y = lineGap/2 + rank * (squareSize + lineGap);
134 x = lineGap/2 + file * (squareSize + lineGap);
135 y = lineGap/2 + ((BOARD_HEIGHT-1)-rank) *
136 (squareSize + lineGap);
139 DrawBorder(x,y, type);
142 int hi1X = -1, hi1Y = -1, hi2X = -1, hi2Y = -1;
143 int pm1X = -1, pm1Y = -1, pm2X = -1, pm2Y = -1;
146 SetHighlights (int fromX, int fromY, int toX, int toY)
148 if (hi1X != fromX || hi1Y != fromY) {
149 if (hi1X >= 0 && hi1Y >= 0) {
150 drawHighlight(hi1X, hi1Y, 0);
152 } // [HGM] first erase both, then draw new!
154 if (hi2X != toX || hi2Y != toY) {
155 if (hi2X >= 0 && hi2Y >= 0) {
156 drawHighlight(hi2X, hi2Y, 0);
159 if (hi1X != fromX || hi1Y != fromY) {
160 if (fromX >= 0 && fromY >= 0) {
161 drawHighlight(fromX, fromY, 1);
164 if (hi2X != toX || hi2Y != toY) {
165 if (toX >= 0 && toY >= 0) {
166 drawHighlight(toX, toY, 1);
170 if(toX<0) // clearing the highlights must have damaged arrow
171 DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y); // for now, redraw it (should really be cleared!)
182 SetHighlights(-1, -1, -1, -1);
187 SetPremoveHighlights (int fromX, int fromY, int toX, int toY)
189 if (pm1X != fromX || pm1Y != fromY) {
190 if (pm1X >= 0 && pm1Y >= 0) {
191 drawHighlight(pm1X, pm1Y, 0);
193 if (fromX >= 0 && fromY >= 0) {
194 drawHighlight(fromX, fromY, 2);
197 if (pm2X != toX || pm2Y != toY) {
198 if (pm2X >= 0 && pm2Y >= 0) {
199 drawHighlight(pm2X, pm2Y, 0);
201 if (toX >= 0 && toY >= 0) {
202 drawHighlight(toX, toY, 2);
212 ClearPremoveHighlights ()
214 SetPremoveHighlights(-1, -1, -1, -1);
218 * If the user selects on a border boundary, return -1; if off the board,
219 * return -2. Otherwise map the event coordinate to the square.
222 EventToSquare (int x, int limit)
229 if ((x % (squareSize + lineGap)) >= squareSize)
231 x /= (squareSize + lineGap);
237 /* [HR] determine square color depending on chess variant. */
239 SquareColor (int row, int column)
243 if (gameInfo.variant == VariantXiangqi) {
244 if (column >= 3 && column <= 5 && row >= 0 && row <= 2) {
246 } else if (column >= 3 && column <= 5 && row >= 7 && row <= 9) {
248 } else if (row <= 4) {
254 square_color = ((column + row) % 2) == 1;
257 /* [hgm] holdings: next line makes all holdings squares light */
258 if(column < BOARD_LEFT || column >= BOARD_RGHT) square_color = 1;
260 if ( // [HGM] holdings: blank out area between board and holdings
261 column == BOARD_LEFT-1
262 || column == BOARD_RGHT
263 || (column == BOARD_LEFT-2 && row < BOARD_HEIGHT-gameInfo.holdingsSize)
264 || (column == BOARD_RGHT+1 && row >= gameInfo.holdingsSize) )
265 square_color = 2; // black
270 /* Convert board position to corner of screen rect and color */
273 ScreenSquare (int column, int row, Pnt *pt, int *color)
276 pt->x = lineGap + ((BOARD_WIDTH-1)-column) * (squareSize + lineGap);
277 pt->y = lineGap + row * (squareSize + lineGap);
279 pt->x = lineGap + column * (squareSize + lineGap);
280 pt->y = lineGap + ((BOARD_HEIGHT-1)-row) * (squareSize + lineGap);
282 *color = SquareColor(row, column);
285 /* Convert window coords to square */
288 BoardSquare (int x, int y, int *column, int *row)
290 *column = EventToSquare(x, BOARD_WIDTH);
291 if (flipView && *column >= 0)
292 *column = BOARD_WIDTH - 1 - *column;
293 *row = EventToSquare(y, BOARD_HEIGHT);
294 if (!flipView && *row >= 0)
295 *row = BOARD_HEIGHT - 1 - *row;
298 /* Generate a series of frame coords from start->mid->finish.
299 The movement rate doubles until the half way point is
300 reached, then halves back down to the final destination,
301 which gives a nice slow in/out effect. The algorithmn
302 may seem to generate too many intermediates for short
303 moves, but remember that the purpose is to attract the
304 viewers attention to the piece about to be moved and
305 then to where it ends up. Too few frames would be less
309 Tween (Pnt *start, Pnt *mid, Pnt *finish, int factor, Pnt frames[], int *nFrames)
311 int fraction, n, count;
315 /* Slow in, stepping 1/16th, then 1/8th, ... */
317 for (n = 0; n < factor; n++)
319 for (n = 0; n < factor; n++) {
320 frames[count].x = start->x + (mid->x - start->x) / fraction;
321 frames[count].y = start->y + (mid->y - start->y) / fraction;
323 fraction = fraction / 2;
327 frames[count] = *mid;
330 /* Slow out, stepping 1/2, then 1/4, ... */
332 for (n = 0; n < factor; n++) {
333 frames[count].x = finish->x - (finish->x - mid->x) / fraction;
334 frames[count].y = finish->y - (finish->y - mid->y) / fraction;
336 fraction = fraction * 2;
341 /**** Animation code by Hugh Fisher, DCS, ANU.
343 Known problem: if a window overlapping the board is
344 moved away while a piece is being animated underneath,
345 the newly exposed area won't be updated properly.
346 I can live with this.
348 Known problem: if you look carefully at the animation
349 of pieces in mono mode, they are being drawn as solid
350 shapes without interior detail while moving. Fixing
351 this would be a major complication for minimal return.
356 #undef Max /* just in case */
358 #define Max(a, b) ((a) > (b) ? (a) : (b))
359 #define Min(a, b) ((a) < (b) ? (a) : (b))
362 short int x, y, width, height;
372 SetRect (MyRectangle *rect, int x, int y, int width, int height)
377 rect->height = height;
380 /* Test if two frames overlap. If they do, return
381 intersection rect within old and location of
382 that rect within new. */
385 Intersect ( Pnt *old, Pnt *new, int size, MyRectangle *area, Pnt *pt)
387 if (old->x > new->x + size || new->x > old->x + size ||
388 old->y > new->y + size || new->y > old->y + size) {
391 SetRect(area, Max(new->x - old->x, 0), Max(new->y - old->y, 0),
392 size - abs(old->x - new->x), size - abs(old->y - new->y));
393 pt->x = Max(old->x - new->x, 0);
394 pt->y = Max(old->y - new->y, 0);
399 /* For two overlapping frames, return the rect(s)
400 in the old that do not intersect with the new. */
403 CalcUpdateRects (Pnt *old, Pnt *new, int size, MyRectangle update[], int *nUpdates)
407 /* If old = new (shouldn't happen) then nothing to draw */
408 if (old->x == new->x && old->y == new->y) {
412 /* Work out what bits overlap. Since we know the rects
413 are the same size we don't need a full intersect calc. */
415 /* Top or bottom edge? */
416 if (new->y > old->y) {
417 SetRect(&(update[count]), old->x, old->y, size, new->y - old->y);
419 } else if (old->y > new->y) {
420 SetRect(&(update[count]), old->x, old->y + size - (old->y - new->y),
421 size, old->y - new->y);
424 /* Left or right edge - don't overlap any update calculated above. */
425 if (new->x > old->x) {
426 SetRect(&(update[count]), old->x, Max(new->y, old->y),
427 new->x - old->x, size - abs(new->y - old->y));
429 } else if (old->x > new->x) {
430 SetRect(&(update[count]), new->x + size, Max(new->y, old->y),
431 old->x - new->x, size - abs(new->y - old->y));
438 /* Animate the movement of a single piece */
441 BeginAnimation (AnimNr anr, ChessSquare piece, ChessSquare bgPiece, int startColor, Pnt *start)
443 AnimState *anim = &anims[anr];
445 if(appData.upsideDown && flipView) piece += piece < BlackPawn ? BlackPawn : -BlackPawn;
446 /* The old buffer is initialised with the start square (empty) */
447 if(bgPiece == EmptySquare) {
448 DrawBlank(anr, start->x, start->y, startColor);
450 /* Kludge alert: When gating we want the introduced
451 piece to appear on the from square. To generate an
452 image of it, we draw it on the board, copy the image,
453 and draw the original piece again. */
454 if(piece != bgPiece) DrawSquare(anim->startBoardY, anim->startBoardX, bgPiece, 0);
455 CopyRectangle(anr, DISP, 2,
456 start->x, start->y, squareSize, squareSize,
457 0, 0); // [HGM] zh: unstack in stead of grab
458 if(piece != bgPiece) DrawSquare(anim->startBoardY, anim->startBoardX, piece, 0);
460 anim->prevFrame = *start;
462 SetDragPiece(anr, piece);
466 AnimationFrame (AnimNr anr, Pnt *frame, ChessSquare piece)
468 MyRectangle updates[4];
472 AnimState *anim = &anims[anr];
474 /* Save what we are about to draw into the new buffer */
475 CopyRectangle(anr, DISP, 0,
476 frame->x, frame->y, squareSize, squareSize,
479 /* Erase bits of the previous frame */
480 if (Intersect(&anim->prevFrame, frame, squareSize, &overlap, &pt)) {
481 /* Where the new frame overlapped the previous,
482 the contents in newBuf are wrong. */
483 CopyRectangle(anr, 2, 0,
484 overlap.x, overlap.y,
485 overlap.width, overlap.height,
487 /* Repaint the areas in the old that don't overlap new */
488 CalcUpdateRects(&anim->prevFrame, frame, squareSize, updates, &count);
489 for (i = 0; i < count; i++)
490 CopyRectangle(anr, 2, DISP,
491 updates[i].x - anim->prevFrame.x,
492 updates[i].y - anim->prevFrame.y,
493 updates[i].width, updates[i].height,
494 updates[i].x, updates[i].y);
496 /* Easy when no overlap */
497 CopyRectangle(anr, 2, DISP,
498 0, 0, squareSize, squareSize,
499 anim->prevFrame.x, anim->prevFrame.y);
502 /* Save this frame for next time round */
503 CopyRectangle(anr, 0, 2,
504 0, 0, squareSize, squareSize,
506 anim->prevFrame = *frame;
508 /* Draw piece over original screen contents, not current,
509 and copy entire rect. Wipes out overlapping piece images. */
510 InsertPiece(anr, piece);
511 CopyRectangle(anr, 0, DISP,
512 0, 0, squareSize, squareSize,
517 EndAnimation (AnimNr anr, Pnt *finish)
519 MyRectangle updates[4];
523 AnimState *anim = &anims[anr];
525 /* The main code will redraw the final square, so we
526 only need to erase the bits that don't overlap. */
527 if (Intersect(&anim->prevFrame, finish, squareSize, &overlap, &pt)) {
528 CalcUpdateRects(&anim->prevFrame, finish, squareSize, updates, &count);
529 for (i = 0; i < count; i++)
530 CopyRectangle(anr, 2, DISP,
531 updates[i].x - anim->prevFrame.x,
532 updates[i].y - anim->prevFrame.y,
533 updates[i].width, updates[i].height,
534 updates[i].x, updates[i].y);
536 CopyRectangle(anr, 2, DISP,
537 0, 0, squareSize, squareSize,
538 anim->prevFrame.x, anim->prevFrame.y);
543 FrameSequence (AnimNr anr, ChessSquare piece, int startColor, Pnt *start, Pnt *finish, Pnt frames[], int nFrames)
547 BeginAnimation(anr, piece, EmptySquare, startColor, start);
548 for (n = 0; n < nFrames; n++) {
549 AnimationFrame(anr, &(frames[n]), piece);
550 FrameDelay(appData.animSpeed);
552 EndAnimation(anr, finish);
556 AnimateAtomicCapture (Board board, int fromX, int fromY, int toX, int toY)
559 ChessSquare piece = board[fromY][toY];
560 board[fromY][toY] = EmptySquare;
561 DrawPosition(FALSE, board);
563 x = lineGap + ((BOARD_WIDTH-1)-toX) * (squareSize + lineGap);
564 y = lineGap + toY * (squareSize + lineGap);
566 x = lineGap + toX * (squareSize + lineGap);
567 y = lineGap + ((BOARD_HEIGHT-1)-toY) * (squareSize + lineGap);
569 for(i=1; i<4*kFactor; i++) {
570 int r = squareSize * 9 * i/(20*kFactor - 5);
571 DrawDot(1, x + squareSize/2 - r, y+squareSize/2 - r, 2*r);
572 FrameDelay(appData.animSpeed);
574 board[fromY][toY] = piece;
577 /* Main control logic for deciding what to animate and how */
580 AnimateMove (Board board, int fromX, int fromY, int toX, int toY)
584 Pnt start, finish, mid;
585 Pnt frames[kFactor * 2 + 1];
586 int nFrames, startColor, endColor;
588 /* Are we animating? */
589 if (!appData.animate || appData.blindfold)
592 if(board[toY][toX] == WhiteRook && board[fromY][fromX] == WhiteKing ||
593 board[toY][toX] == BlackRook && board[fromY][fromX] == BlackKing)
594 return; // [HGM] FRC: no animtion of FRC castlings, as to-square is not true to-square
596 if (fromY < 0 || fromX < 0 || toX < 0 || toY < 0) return;
597 piece = board[fromY][fromX];
598 if (piece >= EmptySquare) return;
603 hop = abs(fromX-toX) == 1 && abs(fromY-toY) == 2 || abs(fromX-toX) == 2 && abs(fromY-toY) == 1;
606 ScreenSquare(fromX, fromY, &start, &startColor);
607 ScreenSquare(toX, toY, &finish, &endColor);
610 /* Knight: make straight movement then diagonal */
611 if (abs(toY - fromY) < abs(toX - fromX)) {
612 mid.x = start.x + (finish.x - start.x) / 2;
616 mid.y = start.y + (finish.y - start.y) / 2;
619 mid.x = start.x + (finish.x - start.x) / 2;
620 mid.y = start.y + (finish.y - start.y) / 2;
623 /* Don't use as many frames for very short moves */
624 if (abs(toY - fromY) + abs(toX - fromX) <= 2)
625 Tween(&start, &mid, &finish, kFactor - 1, frames, &nFrames);
627 Tween(&start, &mid, &finish, kFactor, frames, &nFrames);
628 FrameSequence(Game, piece, startColor, &start, &finish, frames, nFrames);
629 if(Explode(board, fromX, fromY, toX, toY)) { // mark as damaged
631 for(i=0; i<BOARD_WIDTH; i++) for(j=0; j<BOARD_HEIGHT; j++)
632 if((i-toX)*(i-toX) + (j-toY)*(j-toY) < 6) damage[0][j][i] = True;
635 /* Be sure end square is redrawn */
636 damage[0][toY][toX] = True;
640 ChangeDragPiece (ChessSquare piece)
642 anims[Player].dragPiece = piece;
643 SetDragPiece(Player, piece);
647 DragPieceMove (int x, int y)
651 /* Are we animating? */
652 if (!appData.animateDragging || appData.blindfold)
656 if (! anims[Player].dragActive)
658 /* Move piece, maintaining same relative position
659 of mouse within square */
660 corner.x = x - anims[Player].mouseDelta.x;
661 corner.y = y - anims[Player].mouseDelta.y;
662 AnimationFrame(Player, &corner, anims[Player].dragPiece);
664 if (appData.highlightDragging) {
666 BoardSquare(x, y, &boardX, &boardY);
667 SetHighlights(fromX, fromY, boardX, boardY);
673 DragPieceEnd (int x, int y)
675 int boardX, boardY, color;
678 /* Are we animating? */
679 if (!appData.animateDragging || appData.blindfold)
683 if (! anims[Player].dragActive)
685 /* Last frame in sequence is square piece is
686 placed on, which may not match mouse exactly. */
687 BoardSquare(x, y, &boardX, &boardY);
688 ScreenSquare(boardX, boardY, &corner, &color);
689 EndAnimation(Player, &corner);
691 /* Be sure end square is redrawn */
692 damage[0][boardY][boardX] = True;
694 /* This prevents weird things happening with fast successive
695 clicks which on my Sun at least can cause motion events
696 without corresponding press/release. */
697 anims[Player].dragActive = False;
701 DragPieceBegin (int x, int y, Boolean instantly)
703 int boardX, boardY, color;
706 /* Are we animating? */
707 if (!appData.animateDragging || appData.blindfold)
710 /* Figure out which square we start in and the
711 mouse position relative to top left corner. */
712 BoardSquare(x, y, &boardX, &boardY);
713 anims[Player].startBoardX = boardX;
714 anims[Player].startBoardY = boardY;
715 ScreenSquare(boardX, boardY, &corner, &color);
716 anims[Player].startSquare = corner;
717 anims[Player].startColor = color;
718 /* As soon as we start dragging, the piece will jump slightly to
719 be centered over the mouse pointer. */
720 anims[Player].mouseDelta.x = squareSize/2;
721 anims[Player].mouseDelta.y = squareSize/2;
722 /* Initialise animation */
723 anims[Player].dragPiece = PieceForSquare(boardX, boardY);
725 if (anims[Player].dragPiece >= 0 && anims[Player].dragPiece < EmptySquare) {
726 ChessSquare bgPiece = EmptySquare;
727 anims[Player].dragActive = True;
728 if(boardX == BOARD_RGHT+1 && PieceForSquare(boardX-1, boardY) > 1 ||
729 boardX == BOARD_LEFT-2 && PieceForSquare(boardX+1, boardY) > 1)
730 bgPiece = anims[Player].dragPiece;
731 if(gatingPiece != EmptySquare) bgPiece = gatingPiece;
732 BeginAnimation(Player, anims[Player].dragPiece, bgPiece, color, &corner);
733 /* Mark this square as needing to be redrawn. Note that
734 we don't remove the piece though, since logically (ie
735 as seen by opponent) the move hasn't been made yet. */
736 damage[0][boardY][boardX] = True;
738 anims[Player].dragActive = False;
742 /* Handle expose event while piece being dragged */
747 if (!anims[Player].dragActive || appData.blindfold)
750 /* What we're doing: logically, the move hasn't been made yet,
751 so the piece is still in it's original square. But visually
752 it's being dragged around the board. So we erase the square
753 that the piece is on and draw it at the last known drag point. */
754 DrawOneSquare(anims[Player].startSquare.x, anims[Player].startSquare.y,
755 EmptySquare, anims[Player].startColor, 0, NULL, 0);
756 AnimationFrame(Player, &anims[Player].prevFrame, anims[Player].dragPiece);
757 damage[0][anims[Player].startBoardY][anims[Player].startBoardX] = TRUE;
761 DrawSquare (int row, int column, ChessSquare piece, int do_flash)
763 int square_color, x, y, align=0;
768 /* Calculate delay in milliseconds (2-delays per complete flash) */
769 flash_delay = 500 / appData.flashRate;
772 x = lineGap + ((BOARD_WIDTH-1)-column) *
773 (squareSize + lineGap);
774 y = lineGap + row * (squareSize + lineGap);
776 x = lineGap + column * (squareSize + lineGap);
777 y = lineGap + ((BOARD_HEIGHT-1)-row) *
778 (squareSize + lineGap);
781 if(twoBoards && partnerUp) x += hOffset; // [HGM] dual: draw second board
783 square_color = SquareColor(row, column);
785 string[1] = NULLCHAR;
786 if (appData.showCoords && row == (flipView ? BOARD_HEIGHT-1 : 0)
787 && column >= BOARD_LEFT && column < BOARD_RGHT) {
788 string[0] = 'a' + column - BOARD_LEFT;
789 align = 1; // coord in lower-right corner
791 if (appData.showCoords && column == (flipView ? BOARD_RGHT-1 : BOARD_LEFT)) {
792 string[0] = ONE + row;
793 align = 2; // coord in upper-left corner
795 if (column == (flipView ? BOARD_LEFT-1 : BOARD_RGHT) && piece > 1 ) {
796 string[0] = '0' + piece;
797 align = 3; // holdings count in upper-right corner
799 if (column == (flipView ? BOARD_RGHT : BOARD_LEFT-1) && piece > 1) {
800 string[0] = '0' + piece;
801 align = 4; // holdings count in upper-left corner
803 if(square_color == 2 || appData.blindfold) piece = EmptySquare;
805 if (do_flash && piece != EmptySquare && appData.flashCount > 0) {
806 for (i=0; i<appData.flashCount; ++i) {
807 DrawOneSquare(x, y, piece, square_color, 0, string, 0);
808 FlashDelay(flash_delay);
809 DrawOneSquare(x, y, EmptySquare, square_color, 0, string, 0);
810 FlashDelay(flash_delay);
813 DrawOneSquare(x, y, piece, square_color, partnerUp ? 0 : marker[row][column], string, align);
816 /* Returns 1 if there are "too many" differences between b1 and b2
817 (i.e. more than 1 move was made) */
819 too_many_diffs (Board b1, Board b2)
824 for (i=0; i<BOARD_HEIGHT; ++i) {
825 for (j=0; j<BOARD_WIDTH; ++j) {
826 if (b1[i][j] != b2[i][j]) {
827 if (++c > 4) /* Castling causes 4 diffs */
835 /* Matrix describing castling maneuvers */
836 /* Row, ColRookFrom, ColKingFrom, ColRookTo, ColKingTo */
837 static int castling_matrix[4][5] = {
838 { 0, 0, 4, 3, 2 }, /* 0-0-0, white */
839 { 0, 7, 4, 5, 6 }, /* 0-0, white */
840 { 7, 0, 4, 3, 2 }, /* 0-0-0, black */
841 { 7, 7, 4, 5, 6 } /* 0-0, black */
844 /* Checks whether castling occurred. If it did, *rrow and *rcol
845 are set to the destination (row,col) of the rook that moved.
847 Returns 1 if castling occurred, 0 if not.
849 Note: Only handles a max of 1 castling move, so be sure
850 to call too_many_diffs() first.
853 check_castle_draw (Board newb, Board oldb, int *rrow, int *rcol)
858 /* For each type of castling... */
859 for (i=0; i<4; ++i) {
860 r = castling_matrix[i];
862 /* Check the 4 squares involved in the castling move */
864 for (j=1; j<=4; ++j) {
865 if (newb[r[0]][r[j]] == oldb[r[0]][r[j]]) {
872 /* All 4 changed, so it must be a castling move */
882 DrawPosition (int repaint, Board board)
885 static int lastFlipView = 0;
886 static int lastBoardValid[2] = {0, 0};
887 static Board lastBoard[2];
890 int nr = twoBoards*partnerUp;
892 if(DrawSeekGraph()) return; // [HGM] seekgraph: suppress any drawing if seek graph up
895 if (!lastBoardValid[nr]) return;
896 board = lastBoard[nr];
898 if (!lastBoardValid[nr] || (nr == 0 && lastFlipView != flipView)) {
899 MarkMenuItem("View.Flip View", flipView);
903 * It would be simpler to clear the window with XClearWindow()
904 * but this causes a very distracting flicker.
907 if (!repaint && lastBoardValid[nr] && (nr == 1 || lastFlipView == flipView)) {
909 if ( lineGap && IsDrawArrowEnabled())
912 /* If too much changes (begin observing new game, etc.), don't
914 do_flash = too_many_diffs(board, lastBoard[nr]) ? 0 : 1;
916 /* Special check for castling so we don't flash both the king
917 and the rook (just flash the king). */
919 if (check_castle_draw(board, lastBoard[nr], &rrow, &rcol)) {
920 /* Draw rook with NO flashing. King will be drawn flashing later */
921 DrawSquare(rrow, rcol, board[rrow][rcol], 0);
922 lastBoard[nr][rrow][rcol] = board[rrow][rcol];
926 /* First pass -- Draw (newly) empty squares and repair damage.
927 This prevents you from having a piece show up twice while it
928 is flashing on its new square */
929 for (i = 0; i < BOARD_HEIGHT; i++)
930 for (j = 0; j < BOARD_WIDTH; j++)
931 if ((board[i][j] != lastBoard[nr][i][j] && board[i][j] == EmptySquare)
932 || damage[nr][i][j]) {
933 DrawSquare(i, j, board[i][j], 0);
934 damage[nr][i][j] = False;
937 /* Second pass -- Draw piece(s) in new position and flash them */
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]) {
941 DrawSquare(i, j, board[i][j], do_flash);
945 DrawGrid(twoBoards & partnerUp);
947 for (i = 0; i < BOARD_HEIGHT; i++)
948 for (j = 0; j < BOARD_WIDTH; j++) {
949 DrawSquare(i, j, board[i][j], 0);
950 damage[nr][i][j] = False;
954 CopyBoard(lastBoard[nr], board);
955 lastBoardValid[nr] = 1;
956 if(nr == 0) { // [HGM] dual: no highlights on second board yet
957 lastFlipView = flipView;
959 /* Draw highlights */
960 if (pm1X >= 0 && pm1Y >= 0) {
961 drawHighlight(pm1X, pm1Y, 2);
963 if (pm2X >= 0 && pm2Y >= 0) {
964 drawHighlight(pm2X, pm2Y, 2);
966 if (hi1X >= 0 && hi1Y >= 0) {
967 drawHighlight(hi1X, hi1Y, 1);
969 if (hi2X >= 0 && hi2Y >= 0) {
970 drawHighlight(hi2X, hi2Y, 1);
972 DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y);
974 /* If piece being dragged around board, must redraw that too */
977 FlashDelay(0); // this flushes drawing queue;
980 /* [AS] Arrow highlighting support */
982 static double A_WIDTH = 5; /* Width of arrow body */
984 #define A_HEIGHT_FACTOR 6 /* Length of arrow "point", relative to body width */
985 #define A_WIDTH_FACTOR 3 /* Width of arrow "point", relative to body width */
996 return (int) (x + 0.5);
1000 SquareToPos (int rank, int file, int *x, int *y)
1003 *x = lineGap + ((BOARD_WIDTH-1)-file) * (squareSize + lineGap);
1004 *y = lineGap + rank * (squareSize + lineGap);
1006 *x = lineGap + file * (squareSize + lineGap);
1007 *y = lineGap + ((BOARD_HEIGHT-1)-rank) * (squareSize + lineGap);
1011 /* Draw an arrow between two points using current settings */
1013 DrawArrowBetweenPoints (int s_x, int s_y, int d_x, int d_y)
1016 double dx, dy, j, k, x, y;
1019 int h = (d_y > s_y) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1021 arrow[0].x = s_x + A_WIDTH + 0.5;
1024 arrow[1].x = s_x + A_WIDTH + 0.5;
1025 arrow[1].y = d_y - h;
1027 arrow[2].x = arrow[1].x + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1028 arrow[2].y = d_y - h;
1033 arrow[5].x = arrow[1].x - 2*A_WIDTH + 0.5;
1034 arrow[5].y = d_y - h;
1036 arrow[4].x = arrow[5].x - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1037 arrow[4].y = d_y - h;
1039 arrow[6].x = arrow[1].x - 2*A_WIDTH + 0.5;
1042 else if( d_y == s_y ) {
1043 int w = (d_x > s_x) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1046 arrow[0].y = s_y + A_WIDTH + 0.5;
1048 arrow[1].x = d_x - w;
1049 arrow[1].y = s_y + A_WIDTH + 0.5;
1051 arrow[2].x = d_x - w;
1052 arrow[2].y = arrow[1].y + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1057 arrow[5].x = d_x - w;
1058 arrow[5].y = arrow[1].y - 2*A_WIDTH + 0.5;
1060 arrow[4].x = d_x - w;
1061 arrow[4].y = arrow[5].y - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1064 arrow[6].y = arrow[1].y - 2*A_WIDTH + 0.5;
1067 /* [AS] Needed a lot of paper for this! :-) */
1068 dy = (double) (d_y - s_y) / (double) (d_x - s_x);
1069 dx = (double) (s_x - d_x) / (double) (s_y - d_y);
1071 j = sqrt( Sqr(A_WIDTH) / (1.0 + Sqr(dx)) );
1073 k = sqrt( Sqr(A_WIDTH*A_HEIGHT_FACTOR) / (1.0 + Sqr(dy)) );
1078 arrow[0].x = Round(x - j);
1079 arrow[0].y = Round(y + j*dx);
1081 arrow[1].x = Round(arrow[0].x + 2*j); // [HGM] prevent width to be affected by rounding twice
1082 arrow[1].y = Round(arrow[0].y - 2*j*dx);
1085 x = (double) d_x - k;
1086 y = (double) d_y - k*dy;
1089 x = (double) d_x + k;
1090 y = (double) d_y + k*dy;
1093 x = Round(x); y = Round(y); // [HGM] make sure width of shaft is rounded the same way on both ends
1095 arrow[6].x = Round(x - j);
1096 arrow[6].y = Round(y + j*dx);
1098 arrow[2].x = Round(arrow[6].x + 2*j);
1099 arrow[2].y = Round(arrow[6].y - 2*j*dx);
1101 arrow[3].x = Round(arrow[2].x + j*(A_WIDTH_FACTOR-1));
1102 arrow[3].y = Round(arrow[2].y - j*(A_WIDTH_FACTOR-1)*dx);
1107 arrow[5].x = Round(arrow[6].x - j*(A_WIDTH_FACTOR-1));
1108 arrow[5].y = Round(arrow[6].y + j*(A_WIDTH_FACTOR-1)*dx);
1111 DrawPolygon(arrow, 7);
1112 // Polygon( hdc, arrow, 7 );
1116 ArrowDamage (int s_col, int s_row, int d_col, int d_row)
1119 hor = 64*s_col + 32; vert = 64*s_row + 32;
1120 for(i=0; i<= 64; i++) {
1121 damage[0][vert+6>>6][hor+6>>6] = True;
1122 damage[0][vert-6>>6][hor+6>>6] = True;
1123 damage[0][vert+6>>6][hor-6>>6] = True;
1124 damage[0][vert-6>>6][hor-6>>6] = True;
1125 hor += d_col - s_col; vert += d_row - s_row;
1129 /* [AS] Draw an arrow between two squares */
1131 DrawArrowBetweenSquares (int s_col, int s_row, int d_col, int d_row)
1133 int s_x, s_y, d_x, d_y;
1135 if( s_col == d_col && s_row == d_row ) {
1139 /* Get source and destination points */
1140 SquareToPos( s_row, s_col, &s_x, &s_y);
1141 SquareToPos( d_row, d_col, &d_x, &d_y);
1144 d_y += squareSize / 2 - squareSize / 4; // [HGM] round towards same centers on all sides!
1146 else if( d_y < s_y ) {
1147 d_y += squareSize / 2 + squareSize / 4;
1150 d_y += squareSize / 2;
1154 d_x += squareSize / 2 - squareSize / 4;
1156 else if( d_x < s_x ) {
1157 d_x += squareSize / 2 + squareSize / 4;
1160 d_x += squareSize / 2;
1163 s_x += squareSize / 2;
1164 s_y += squareSize / 2;
1167 A_WIDTH = squareSize / 14.; //[HGM] make float
1169 DrawArrowBetweenPoints( s_x, s_y, d_x, d_y );
1170 ArrowDamage(s_col, s_row, d_col, d_row);
1174 IsDrawArrowEnabled ()
1176 return appData.highlightMoveWithArrow && squareSize >= 32;
1180 DrawArrowHighlight (int fromX, int fromY, int toX,int toY)
1182 if( IsDrawArrowEnabled() && fromX >= 0 && fromY >= 0 && toX >= 0 && toY >= 0)
1183 DrawArrowBetweenSquares(fromX, fromY, toX, toY);