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 && 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 && 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];
898 int nr = twoBoards*partnerUp;
900 if(DrawSeekGraph()) return; // [HGM] seekgraph: suppress any drawing if seek graph up
903 if (!lastBoardValid[nr]) return;
904 board = lastBoard[nr];
906 if (!lastBoardValid[nr] || (nr == 0 && lastFlipView != flipView)) {
907 MarkMenuItem("View.Flip View", flipView);
910 if(nr) { SlavePopUp(); SwitchWindow(); } // [HGM] popup board if not yet popped up, and switch drawing to it.
913 * It would be simpler to clear the window with XClearWindow()
914 * but this causes a very distracting flicker.
917 if (!repaint && lastBoardValid[nr] && (nr == 1 || lastFlipView == flipView)) {
919 if ( lineGap && IsDrawArrowEnabled())
922 /* If too much changes (begin observing new game, etc.), don't
924 do_flash = too_many_diffs(board, lastBoard[nr]) ? 0 : 1;
926 /* Special check for castling so we don't flash both the king
927 and the rook (just flash the king). */
929 if (check_castle_draw(board, lastBoard[nr], &rrow, &rcol)) {
930 /* Draw rook with NO flashing. King will be drawn flashing later */
931 DrawSquare(rrow, rcol, board[rrow][rcol], 0);
932 lastBoard[nr][rrow][rcol] = board[rrow][rcol];
936 /* First pass -- Draw (newly) empty squares and repair damage.
937 This prevents you from having a piece show up twice while it
938 is flashing on its new square */
939 for (i = 0; i < BOARD_HEIGHT; i++)
940 for (j = 0; j < BOARD_WIDTH; j++)
941 if (((board[i][j] != lastBoard[nr][i][j] || !nr && marker[i][j] != lastMarker[i][j]) && board[i][j] == EmptySquare)
942 || damage[nr][i][j]) {
943 DrawSquare(i, j, board[i][j], 0);
944 damage[nr][i][j] = False;
947 /* Second pass -- Draw piece(s) in new position and flash them */
948 for (i = 0; i < BOARD_HEIGHT; i++)
949 for (j = 0; j < BOARD_WIDTH; j++)
950 if (board[i][j] != lastBoard[nr][i][j] || !nr && marker[i][j] != lastMarker[i][j]) {
951 DrawSquare(i, j, board[i][j], do_flash);
957 for (i = 0; i < BOARD_HEIGHT; i++)
958 for (j = 0; j < BOARD_WIDTH; j++) {
959 DrawSquare(i, j, board[i][j], 0);
960 damage[nr][i][j] = False;
964 CopyBoard(lastBoard[nr], board);
965 lastBoardValid[nr] = 1;
966 if(nr == 0) { // [HGM] dual: no highlights on second board yet
967 lastFlipView = flipView;
968 for (i = 0; i < BOARD_HEIGHT; i++)
969 for (j = 0; j < BOARD_WIDTH; j++)
970 lastMarker[i][j] = marker[i][j];
972 /* Draw highlights */
973 if (pm1X >= 0 && pm1Y >= 0) {
974 drawHighlight(pm1X, pm1Y, 2);
976 if (pm2X >= 0 && pm2Y >= 0) {
977 drawHighlight(pm2X, pm2Y, 2);
979 if (hi1X >= 0 && hi1Y >= 0) {
980 drawHighlight(hi1X, hi1Y, 1);
982 if (hi2X >= 0 && hi2Y >= 0) {
983 drawHighlight(hi2X, hi2Y, 1);
985 DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y);
987 else DrawArrowHighlight (board[EP_STATUS-3], board[EP_STATUS-4], board[EP_STATUS-1], board[EP_STATUS-2]);
989 /* If piece being dragged around board, must redraw that too */
992 FlashDelay(0); // this flushes drawing queue;
993 if(nr) SwitchWindow();
996 /* [AS] Arrow highlighting support */
998 static double A_WIDTH = 5; /* Width of arrow body */
1000 #define A_HEIGHT_FACTOR 6 /* Length of arrow "point", relative to body width */
1001 #define A_WIDTH_FACTOR 3 /* Width of arrow "point", relative to body width */
1012 return (int) (x + 0.5);
1016 SquareToPos (int rank, int file, int *x, int *y)
1019 *x = lineGap + ((BOARD_WIDTH-1)-file) * (squareSize + lineGap);
1020 *y = lineGap + rank * (squareSize + lineGap);
1022 *x = lineGap + file * (squareSize + lineGap);
1023 *y = lineGap + ((BOARD_HEIGHT-1)-rank) * (squareSize + lineGap);
1027 /* Draw an arrow between two points using current settings */
1029 DrawArrowBetweenPoints (int s_x, int s_y, int d_x, int d_y)
1032 double dx, dy, j, k, x, y;
1035 int h = (d_y > s_y) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1037 arrow[0].x = s_x + A_WIDTH + 0.5;
1040 arrow[1].x = s_x + A_WIDTH + 0.5;
1041 arrow[1].y = d_y - h;
1043 arrow[2].x = arrow[1].x + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1044 arrow[2].y = d_y - h;
1049 arrow[5].x = arrow[1].x - 2*A_WIDTH + 0.5;
1050 arrow[5].y = d_y - h;
1052 arrow[4].x = arrow[5].x - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1053 arrow[4].y = d_y - h;
1055 arrow[6].x = arrow[1].x - 2*A_WIDTH + 0.5;
1058 else if( d_y == s_y ) {
1059 int w = (d_x > s_x) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1062 arrow[0].y = s_y + A_WIDTH + 0.5;
1064 arrow[1].x = d_x - w;
1065 arrow[1].y = s_y + A_WIDTH + 0.5;
1067 arrow[2].x = d_x - w;
1068 arrow[2].y = arrow[1].y + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1073 arrow[5].x = d_x - w;
1074 arrow[5].y = arrow[1].y - 2*A_WIDTH + 0.5;
1076 arrow[4].x = d_x - w;
1077 arrow[4].y = arrow[5].y - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1080 arrow[6].y = arrow[1].y - 2*A_WIDTH + 0.5;
1083 /* [AS] Needed a lot of paper for this! :-) */
1084 dy = (double) (d_y - s_y) / (double) (d_x - s_x);
1085 dx = (double) (s_x - d_x) / (double) (s_y - d_y);
1087 j = sqrt( Sqr(A_WIDTH) / (1.0 + Sqr(dx)) );
1089 k = sqrt( Sqr(A_WIDTH*A_HEIGHT_FACTOR) / (1.0 + Sqr(dy)) );
1094 arrow[0].x = Round(x - j);
1095 arrow[0].y = Round(y + j*dx);
1097 arrow[1].x = Round(arrow[0].x + 2*j); // [HGM] prevent width to be affected by rounding twice
1098 arrow[1].y = Round(arrow[0].y - 2*j*dx);
1101 x = (double) d_x - k;
1102 y = (double) d_y - k*dy;
1105 x = (double) d_x + k;
1106 y = (double) d_y + k*dy;
1109 x = Round(x); y = Round(y); // [HGM] make sure width of shaft is rounded the same way on both ends
1111 arrow[6].x = Round(x - j);
1112 arrow[6].y = Round(y + j*dx);
1114 arrow[2].x = Round(arrow[6].x + 2*j);
1115 arrow[2].y = Round(arrow[6].y - 2*j*dx);
1117 arrow[3].x = Round(arrow[2].x + j*(A_WIDTH_FACTOR-1));
1118 arrow[3].y = Round(arrow[2].y - j*(A_WIDTH_FACTOR-1)*dx);
1123 arrow[5].x = Round(arrow[6].x - j*(A_WIDTH_FACTOR-1));
1124 arrow[5].y = Round(arrow[6].y + j*(A_WIDTH_FACTOR-1)*dx);
1127 DrawPolygon(arrow, 7);
1128 // Polygon( hdc, arrow, 7 );
1132 ArrowDamage (int s_col, int s_row, int d_col, int d_row)
1134 int hor, vert, i, n = partnerUp * twoBoards;
1135 hor = 64*s_col + 32; vert = 64*s_row + 32;
1136 for(i=0; i<= 64; i++) {
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 damage[n][vert-6>>6][hor-6>>6] = True;
1141 hor += d_col - s_col; vert += d_row - s_row;
1145 /* [AS] Draw an arrow between two squares */
1147 DrawArrowBetweenSquares (int s_col, int s_row, int d_col, int d_row)
1149 int s_x, s_y, d_x, d_y;
1151 if( s_col == d_col && s_row == d_row ) {
1155 /* Get source and destination points */
1156 SquareToPos( s_row, s_col, &s_x, &s_y);
1157 SquareToPos( d_row, d_col, &d_x, &d_y);
1160 d_y += squareSize / 2 - squareSize / 4; // [HGM] round towards same centers on all sides!
1162 else if( d_y < s_y ) {
1163 d_y += squareSize / 2 + squareSize / 4;
1166 d_y += squareSize / 2;
1170 d_x += squareSize / 2 - squareSize / 4;
1172 else if( d_x < s_x ) {
1173 d_x += squareSize / 2 + squareSize / 4;
1176 d_x += squareSize / 2;
1179 s_x += squareSize / 2;
1180 s_y += squareSize / 2;
1183 A_WIDTH = squareSize / 14.; //[HGM] make float
1185 DrawArrowBetweenPoints( s_x, s_y, d_x, d_y );
1186 ArrowDamage(s_col, s_row, d_col, d_row);
1190 IsDrawArrowEnabled ()
1192 return (appData.highlightMoveWithArrow || twoBoards && partnerUp) && squareSize >= 32;
1196 DrawArrowHighlight (int fromX, int fromY, int toX,int toY)
1198 if( IsDrawArrowEnabled() && fromX >= 0 && fromY >= 0 && toX >= 0 && toY >= 0)
1199 DrawArrowBetweenSquares(fromX, fromY, toX, toY);