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>
108 #define usleep(t) _sleep2(((t)+500)/1000)
112 int squareSize, lineGap;
114 int damage[2][BOARD_RANKS][BOARD_FILES];
116 /* There can be two pieces being animated at once: a player
117 can begin dragging a piece before the remote opponent has moved. */
119 AnimState anims[NrOfAnims];
121 static void DrawSquare P((int row, int column, ChessSquare piece, int do_flash));
122 static Boolean IsDrawArrowEnabled P((void));
123 static void DrawArrowHighlight P((int fromX, int fromY, int toX,int toY));
124 static void ArrowDamage P((int s_col, int s_row, int d_col, int d_row));
127 drawHighlight (int file, int rank, int type)
131 if (lineGap == 0) return;
134 x = lineGap/2 + ((BOARD_WIDTH-1)-file) *
135 (squareSize + lineGap);
136 y = lineGap/2 + rank * (squareSize + lineGap);
138 x = lineGap/2 + file * (squareSize + lineGap);
139 y = lineGap/2 + ((BOARD_HEIGHT-1)-rank) *
140 (squareSize + lineGap);
143 DrawBorder(x,y, type);
146 int hi1X = -1, hi1Y = -1, hi2X = -1, hi2Y = -1;
147 int pm1X = -1, pm1Y = -1, pm2X = -1, pm2Y = -1;
150 SetHighlights (int fromX, int fromY, int toX, int toY)
152 int arrow = hi2X >= 0 && hi1Y >= 0 && IsDrawArrowEnabled();
154 if (hi1X != fromX || hi1Y != fromY) {
155 if (hi1X >= 0 && hi1Y >= 0) {
156 drawHighlight(hi1X, hi1Y, 0);
158 } // [HGM] first erase both, then draw new!
160 if (hi2X != toX || hi2Y != toY) {
161 if (hi2X >= 0 && hi2Y >= 0) {
162 drawHighlight(hi2X, hi2Y, 0);
166 if(arrow) // there currently is an arrow displayed
167 ArrowDamage(hi1X, hi1Y, hi2X, hi2Y); // mark which squares it damaged
169 if (hi1X != fromX || hi1Y != fromY) {
170 if (fromX >= 0 && fromY >= 0) {
171 drawHighlight(fromX, fromY, 1);
174 if (hi2X != toX || hi2Y != toY) {
175 if (toX >= 0 && toY >= 0) {
176 drawHighlight(toX, toY, 1);
185 if(arrow || toX >= 0 && fromY >= 0 && IsDrawArrowEnabled())
186 DrawPosition(FALSE, NULL); // repair any arrow damage, or draw a new one
192 SetHighlights(-1, -1, -1, -1);
197 SetPremoveHighlights (int fromX, int fromY, int toX, int toY)
199 if (pm1X != fromX || pm1Y != fromY) {
200 if (pm1X >= 0 && pm1Y >= 0) {
201 drawHighlight(pm1X, pm1Y, 0);
203 if (fromX >= 0 && fromY >= 0) {
204 drawHighlight(fromX, fromY, 2);
207 if (pm2X != toX || pm2Y != toY) {
208 if (pm2X >= 0 && pm2Y >= 0) {
209 drawHighlight(pm2X, pm2Y, 0);
211 if (toX >= 0 && toY >= 0) {
212 drawHighlight(toX, toY, 2);
222 ClearPremoveHighlights ()
224 SetPremoveHighlights(-1, -1, -1, -1);
228 * If the user selects on a border boundary, return -1; if off the board,
229 * return -2. Otherwise map the event coordinate to the square.
232 EventToSquare (int x, int limit)
239 if ((x % (squareSize + lineGap)) >= squareSize)
241 x /= (squareSize + lineGap);
247 /* [HR] determine square color depending on chess variant. */
249 SquareColor (int row, int column)
253 if (gameInfo.variant == VariantXiangqi) {
254 if (column >= 3 && column <= 5 && row >= 0 && row <= 2) {
256 } else if (column >= 3 && column <= 5 && row >= 7 && row <= 9) {
258 } else if (row <= 4) {
264 square_color = ((column + row) % 2) == 1;
267 /* [hgm] holdings: next line makes all holdings squares light */
268 if(column < BOARD_LEFT || column >= BOARD_RGHT) square_color = 1;
270 if ( // [HGM] holdings: blank out area between board and holdings
271 column == BOARD_LEFT-1
272 || column == BOARD_RGHT
273 || (column == BOARD_LEFT-2 && row < BOARD_HEIGHT-gameInfo.holdingsSize)
274 || (column == BOARD_RGHT+1 && row >= gameInfo.holdingsSize) )
275 square_color = 2; // black
280 /* Convert board position to corner of screen rect and color */
283 ScreenSquare (int column, int row, Pnt *pt, int *color)
286 pt->x = lineGap + ((BOARD_WIDTH-1)-column) * (squareSize + lineGap);
287 pt->y = lineGap + row * (squareSize + lineGap);
289 pt->x = lineGap + column * (squareSize + lineGap);
290 pt->y = lineGap + ((BOARD_HEIGHT-1)-row) * (squareSize + lineGap);
292 *color = SquareColor(row, column);
295 /* Convert window coords to square */
298 BoardSquare (int x, int y, int *column, int *row)
300 *column = EventToSquare(x, BOARD_WIDTH);
301 if (flipView && *column >= 0)
302 *column = BOARD_WIDTH - 1 - *column;
303 *row = EventToSquare(y, BOARD_HEIGHT);
304 if (!flipView && *row >= 0)
305 *row = BOARD_HEIGHT - 1 - *row;
308 /* Generate a series of frame coords from start->mid->finish.
309 The movement rate doubles until the half way point is
310 reached, then halves back down to the final destination,
311 which gives a nice slow in/out effect. The algorithmn
312 may seem to generate too many intermediates for short
313 moves, but remember that the purpose is to attract the
314 viewers attention to the piece about to be moved and
315 then to where it ends up. Too few frames would be less
319 Tween (Pnt *start, Pnt *mid, Pnt *finish, int factor, Pnt frames[], int *nFrames)
321 int fraction, n, count;
325 /* Slow in, stepping 1/16th, then 1/8th, ... */
327 for (n = 0; n < factor; n++)
329 for (n = 0; n < factor; n++) {
330 frames[count].x = start->x + (mid->x - start->x) / fraction;
331 frames[count].y = start->y + (mid->y - start->y) / fraction;
333 fraction = fraction / 2;
337 frames[count] = *mid;
340 /* Slow out, stepping 1/2, then 1/4, ... */
342 for (n = 0; n < factor; n++) {
343 frames[count].x = finish->x - (finish->x - mid->x) / fraction;
344 frames[count].y = finish->y - (finish->y - mid->y) / fraction;
346 fraction = fraction * 2;
351 /**** Animation code by Hugh Fisher, DCS, ANU.
353 Known problem: if a window overlapping the board is
354 moved away while a piece is being animated underneath,
355 the newly exposed area won't be updated properly.
356 I can live with this.
358 Known problem: if you look carefully at the animation
359 of pieces in mono mode, they are being drawn as solid
360 shapes without interior detail while moving. Fixing
361 this would be a major complication for minimal return.
366 #undef Max /* just in case */
368 #define Max(a, b) ((a) > (b) ? (a) : (b))
369 #define Min(a, b) ((a) < (b) ? (a) : (b))
372 short int x, y, width, height;
382 SetRect (MyRectangle *rect, int x, int y, int width, int height)
387 rect->height = height;
390 /* Test if two frames overlap. If they do, return
391 intersection rect within old and location of
392 that rect within new. */
395 Intersect ( Pnt *old, Pnt *new, int size, MyRectangle *area, Pnt *pt)
397 if (old->x > new->x + size || new->x > old->x + size ||
398 old->y > new->y + size || new->y > old->y + size) {
401 SetRect(area, Max(new->x - old->x, 0), Max(new->y - old->y, 0),
402 size - abs(old->x - new->x), size - abs(old->y - new->y));
403 pt->x = Max(old->x - new->x, 0);
404 pt->y = Max(old->y - new->y, 0);
409 /* For two overlapping frames, return the rect(s)
410 in the old that do not intersect with the new. */
413 CalcUpdateRects (Pnt *old, Pnt *new, int size, MyRectangle update[], int *nUpdates)
417 /* If old = new (shouldn't happen) then nothing to draw */
418 if (old->x == new->x && old->y == new->y) {
422 /* Work out what bits overlap. Since we know the rects
423 are the same size we don't need a full intersect calc. */
425 /* Top or bottom edge? */
426 if (new->y > old->y) {
427 SetRect(&(update[count]), old->x, old->y, size, new->y - old->y);
429 } else if (old->y > new->y) {
430 SetRect(&(update[count]), old->x, old->y + size - (old->y - new->y),
431 size, old->y - new->y);
434 /* Left or right edge - don't overlap any update calculated above. */
435 if (new->x > old->x) {
436 SetRect(&(update[count]), old->x, Max(new->y, old->y),
437 new->x - old->x, size - abs(new->y - old->y));
439 } else if (old->x > new->x) {
440 SetRect(&(update[count]), new->x + size, Max(new->y, old->y),
441 old->x - new->x, size - abs(new->y - old->y));
448 /* Animate the movement of a single piece */
451 BeginAnimation (AnimNr anr, ChessSquare piece, ChessSquare bgPiece, int startColor, Pnt *start)
453 AnimState *anim = &anims[anr];
455 if(appData.upsideDown && flipView) piece += piece < BlackPawn ? BlackPawn : -BlackPawn;
456 /* The old buffer is initialised with the start square (empty) */
457 if(bgPiece == EmptySquare) {
458 DrawBlank(anr, start->x, start->y, startColor);
460 /* Kludge alert: When gating we want the introduced
461 piece to appear on the from square. To generate an
462 image of it, we draw it on the board, copy the image,
463 and draw the original piece again. */
464 if(piece != bgPiece) DrawSquare(anim->startBoardY, anim->startBoardX, bgPiece, 0);
465 CopyRectangle(anr, DISP, 2,
466 start->x, start->y, squareSize, squareSize,
467 0, 0); // [HGM] zh: unstack in stead of grab
468 if(piece != bgPiece) DrawSquare(anim->startBoardY, anim->startBoardX, piece, 0);
470 anim->prevFrame = *start;
472 SetDragPiece(anr, piece);
476 AnimationFrame (AnimNr anr, Pnt *frame, ChessSquare piece)
478 MyRectangle updates[4];
482 AnimState *anim = &anims[anr];
484 /* Save what we are about to draw into the new buffer */
485 CopyRectangle(anr, DISP, 0,
486 frame->x, frame->y, squareSize, squareSize,
489 /* Erase bits of the previous frame */
490 if (Intersect(&anim->prevFrame, frame, squareSize, &overlap, &pt)) {
491 /* Where the new frame overlapped the previous,
492 the contents in newBuf are wrong. */
493 CopyRectangle(anr, 2, 0,
494 overlap.x, overlap.y,
495 overlap.width, overlap.height,
497 /* Repaint the areas in the old that don't overlap new */
498 CalcUpdateRects(&anim->prevFrame, frame, squareSize, updates, &count);
499 for (i = 0; i < count; i++)
500 CopyRectangle(anr, 2, DISP,
501 updates[i].x - anim->prevFrame.x,
502 updates[i].y - anim->prevFrame.y,
503 updates[i].width, updates[i].height,
504 updates[i].x, updates[i].y);
506 /* Easy when no overlap */
507 CopyRectangle(anr, 2, DISP,
508 0, 0, squareSize, squareSize,
509 anim->prevFrame.x, anim->prevFrame.y);
512 /* Save this frame for next time round */
513 CopyRectangle(anr, 0, 2,
514 0, 0, squareSize, squareSize,
516 anim->prevFrame = *frame;
518 /* Draw piece over original screen contents, not current,
519 and copy entire rect. Wipes out overlapping piece images. */
520 InsertPiece(anr, piece);
521 CopyRectangle(anr, 0, DISP,
522 0, 0, squareSize, squareSize,
527 EndAnimation (AnimNr anr, Pnt *finish)
529 MyRectangle updates[4];
533 AnimState *anim = &anims[anr];
535 /* The main code will redraw the final square, so we
536 only need to erase the bits that don't overlap. */
537 if (Intersect(&anim->prevFrame, finish, squareSize, &overlap, &pt)) {
538 CalcUpdateRects(&anim->prevFrame, finish, squareSize, updates, &count);
539 for (i = 0; i < count; i++)
540 CopyRectangle(anr, 2, DISP,
541 updates[i].x - anim->prevFrame.x,
542 updates[i].y - anim->prevFrame.y,
543 updates[i].width, updates[i].height,
544 updates[i].x, updates[i].y);
546 CopyRectangle(anr, 2, DISP,
547 0, 0, squareSize, squareSize,
548 anim->prevFrame.x, anim->prevFrame.y);
553 FrameSequence (AnimNr anr, ChessSquare piece, int startColor, Pnt *start, Pnt *finish, Pnt frames[], int nFrames)
557 BeginAnimation(anr, piece, EmptySquare, startColor, start);
558 for (n = 0; n < nFrames; n++) {
559 AnimationFrame(anr, &(frames[n]), piece);
560 FrameDelay(appData.animSpeed);
562 EndAnimation(anr, finish);
566 AnimateAtomicCapture (Board board, int fromX, int fromY, int toX, int toY)
569 ChessSquare piece = board[fromY][toY];
570 board[fromY][toY] = EmptySquare;
571 DrawPosition(FALSE, board);
573 x = lineGap + ((BOARD_WIDTH-1)-toX) * (squareSize + lineGap);
574 y = lineGap + toY * (squareSize + lineGap);
576 x = lineGap + toX * (squareSize + lineGap);
577 y = lineGap + ((BOARD_HEIGHT-1)-toY) * (squareSize + lineGap);
579 for(i=1; i<4*kFactor; i++) {
580 int r = squareSize * 9 * i/(20*kFactor - 5);
581 DrawDot(1, x + squareSize/2 - r, y+squareSize/2 - r, 2*r);
582 FrameDelay(appData.animSpeed);
584 board[fromY][toY] = piece;
587 /* Main control logic for deciding what to animate and how */
590 AnimateMove (Board board, int fromX, int fromY, int toX, int toY)
594 Pnt start, finish, mid;
595 Pnt frames[kFactor * 2 + 1];
596 int nFrames, startColor, endColor;
598 /* Are we animating? */
599 if (!appData.animate || appData.blindfold)
602 if(board[toY][toX] == WhiteRook && board[fromY][fromX] == WhiteKing ||
603 board[toY][toX] == BlackRook && board[fromY][fromX] == BlackKing)
604 return; // [HGM] FRC: no animtion of FRC castlings, as to-square is not true to-square
606 if (fromY < 0 || fromX < 0 || toX < 0 || toY < 0) return;
607 piece = board[fromY][fromX];
608 if (piece >= EmptySquare) return;
613 hop = abs(fromX-toX) == 1 && abs(fromY-toY) == 2 || abs(fromX-toX) == 2 && abs(fromY-toY) == 1;
616 ScreenSquare(fromX, fromY, &start, &startColor);
617 ScreenSquare(toX, toY, &finish, &endColor);
620 /* Knight: make straight movement then diagonal */
621 if (abs(toY - fromY) < abs(toX - fromX)) {
622 mid.x = start.x + (finish.x - start.x) / 2;
626 mid.y = start.y + (finish.y - start.y) / 2;
629 mid.x = start.x + (finish.x - start.x) / 2;
630 mid.y = start.y + (finish.y - start.y) / 2;
633 /* Don't use as many frames for very short moves */
634 if (abs(toY - fromY) + abs(toX - fromX) <= 2)
635 Tween(&start, &mid, &finish, kFactor - 1, frames, &nFrames);
637 Tween(&start, &mid, &finish, kFactor, frames, &nFrames);
638 FrameSequence(Game, piece, startColor, &start, &finish, frames, nFrames);
639 if(Explode(board, fromX, fromY, toX, toY)) { // mark as damaged
641 for(i=0; i<BOARD_WIDTH; i++) for(j=0; j<BOARD_HEIGHT; j++)
642 if((i-toX)*(i-toX) + (j-toY)*(j-toY) < 6) damage[0][j][i] = True;
645 /* Be sure end square is redrawn */
646 damage[0][toY][toX] = True;
650 ChangeDragPiece (ChessSquare piece)
652 anims[Player].dragPiece = piece;
653 SetDragPiece(Player, piece);
657 DragPieceMove (int x, int y)
661 /* Are we animating? */
662 if (!appData.animateDragging || appData.blindfold)
666 if (! anims[Player].dragActive)
668 /* Move piece, maintaining same relative position
669 of mouse within square */
670 corner.x = x - anims[Player].mouseDelta.x;
671 corner.y = y - anims[Player].mouseDelta.y;
672 AnimationFrame(Player, &corner, anims[Player].dragPiece);
674 if (appData.highlightDragging) {
676 BoardSquare(x, y, &boardX, &boardY);
677 SetHighlights(fromX, fromY, boardX, boardY);
683 DragPieceEnd (int x, int y)
685 int boardX, boardY, color;
688 /* Are we animating? */
689 if (!appData.animateDragging || appData.blindfold)
693 if (! anims[Player].dragActive)
695 /* Last frame in sequence is square piece is
696 placed on, which may not match mouse exactly. */
697 BoardSquare(x, y, &boardX, &boardY);
698 ScreenSquare(boardX, boardY, &corner, &color);
699 EndAnimation(Player, &corner);
701 /* Be sure end square is redrawn */
702 damage[0][boardY][boardX] = True;
704 /* This prevents weird things happening with fast successive
705 clicks which on my Sun at least can cause motion events
706 without corresponding press/release. */
707 anims[Player].dragActive = False;
711 DragPieceBegin (int x, int y, Boolean instantly)
713 int boardX, boardY, color;
716 /* Are we animating? */
717 if (!appData.animateDragging || appData.blindfold)
720 /* Figure out which square we start in and the
721 mouse position relative to top left corner. */
722 BoardSquare(x, y, &boardX, &boardY);
723 anims[Player].startBoardX = boardX;
724 anims[Player].startBoardY = boardY;
725 ScreenSquare(boardX, boardY, &corner, &color);
726 anims[Player].startSquare = corner;
727 anims[Player].startColor = color;
728 /* As soon as we start dragging, the piece will jump slightly to
729 be centered over the mouse pointer. */
730 anims[Player].mouseDelta.x = squareSize/2;
731 anims[Player].mouseDelta.y = squareSize/2;
732 /* Initialise animation */
733 anims[Player].dragPiece = PieceForSquare(boardX, boardY);
735 if (anims[Player].dragPiece >= 0 && anims[Player].dragPiece < EmptySquare) {
736 ChessSquare bgPiece = EmptySquare;
737 anims[Player].dragActive = True;
738 if(boardX == BOARD_RGHT+1 && PieceForSquare(boardX-1, boardY) > 1 ||
739 boardX == BOARD_LEFT-2 && PieceForSquare(boardX+1, boardY) > 1)
740 bgPiece = anims[Player].dragPiece;
741 if(gatingPiece != EmptySquare) bgPiece = gatingPiece;
742 BeginAnimation(Player, anims[Player].dragPiece, bgPiece, color, &corner);
743 /* Mark this square as needing to be redrawn. Note that
744 we don't remove the piece though, since logically (ie
745 as seen by opponent) the move hasn't been made yet. */
746 damage[0][boardY][boardX] = True;
748 anims[Player].dragActive = False;
752 /* Handle expose event while piece being dragged */
757 if (!anims[Player].dragActive || appData.blindfold)
760 /* What we're doing: logically, the move hasn't been made yet,
761 so the piece is still in it's original square. But visually
762 it's being dragged around the board. So we erase the square
763 that the piece is on and draw it at the last known drag point. */
764 DrawOneSquare(anims[Player].startSquare.x, anims[Player].startSquare.y,
765 EmptySquare, anims[Player].startColor, 0, NULL, 0);
766 AnimationFrame(Player, &anims[Player].prevFrame, anims[Player].dragPiece);
767 damage[0][anims[Player].startBoardY][anims[Player].startBoardX] = TRUE;
771 DrawSquare (int row, int column, ChessSquare piece, int do_flash)
773 int square_color, x, y, align=0;
778 /* Calculate delay in milliseconds (2-delays per complete flash) */
779 flash_delay = 500 / appData.flashRate;
782 x = lineGap + ((BOARD_WIDTH-1)-column) *
783 (squareSize + lineGap);
784 y = lineGap + row * (squareSize + lineGap);
786 x = lineGap + column * (squareSize + lineGap);
787 y = lineGap + ((BOARD_HEIGHT-1)-row) *
788 (squareSize + lineGap);
791 square_color = SquareColor(row, column);
793 string[1] = NULLCHAR;
794 if (appData.showCoords && row == (flipView ? BOARD_HEIGHT-1 : 0)
795 && column >= BOARD_LEFT && column < BOARD_RGHT) {
796 string[0] = 'a' + column - BOARD_LEFT;
797 align = 1; // coord in lower-right corner
799 if (appData.showCoords && column == (flipView ? BOARD_RGHT-1 : BOARD_LEFT)) {
800 string[0] = ONE + row;
801 align = 2; // coord in upper-left corner
803 if (column == (flipView ? BOARD_LEFT-1 : BOARD_RGHT) && piece > 1 ) {
804 string[0] = '0' + piece;
805 align = 3; // holdings count in upper-right corner
807 if (column == (flipView ? BOARD_RGHT : BOARD_LEFT-1) && piece > 1) {
808 string[0] = '0' + piece;
809 align = 4; // holdings count in upper-left corner
811 if(square_color == 2 || appData.blindfold) piece = EmptySquare;
813 if (do_flash && piece != EmptySquare && appData.flashCount > 0) {
814 for (i=0; i<appData.flashCount; ++i) {
815 DrawOneSquare(x, y, piece, square_color, 0, string, 0);
816 GraphExpose(currBoard, x, y, squareSize, squareSize);
817 FlashDelay(flash_delay);
818 DrawOneSquare(x, y, EmptySquare, square_color, 0, string, 0);
819 GraphExpose(currBoard, x, y, squareSize, squareSize);
820 FlashDelay(flash_delay);
823 DrawOneSquare(x, y, piece, square_color, partnerUp ? 0 : marker[row][column], string, align);
826 /* Returns 1 if there are "too many" differences between b1 and b2
827 (i.e. more than 1 move was made) */
829 too_many_diffs (Board b1, Board b2)
834 for (i=0; i<BOARD_HEIGHT; ++i) {
835 for (j=0; j<BOARD_WIDTH; ++j) {
836 if (b1[i][j] != b2[i][j]) {
837 if (++c > 4) /* Castling causes 4 diffs */
845 /* Matrix describing castling maneuvers */
846 /* Row, ColRookFrom, ColKingFrom, ColRookTo, ColKingTo */
847 static int castling_matrix[4][5] = {
848 { 0, 0, 4, 3, 2 }, /* 0-0-0, white */
849 { 0, 7, 4, 5, 6 }, /* 0-0, white */
850 { 7, 0, 4, 3, 2 }, /* 0-0-0, black */
851 { 7, 7, 4, 5, 6 } /* 0-0, black */
854 /* Checks whether castling occurred. If it did, *rrow and *rcol
855 are set to the destination (row,col) of the rook that moved.
857 Returns 1 if castling occurred, 0 if not.
859 Note: Only handles a max of 1 castling move, so be sure
860 to call too_many_diffs() first.
863 check_castle_draw (Board newb, Board oldb, int *rrow, int *rcol)
868 /* For each type of castling... */
869 for (i=0; i<4; ++i) {
870 r = castling_matrix[i];
872 /* Check the 4 squares involved in the castling move */
874 for (j=1; j<=4; ++j) {
875 if (newb[r[0]][r[j]] == oldb[r[0]][r[j]]) {
882 /* All 4 changed, so it must be a castling move */
892 DrawPosition (int repaint, Board board)
894 int i, j, do_flash, exposeAll = False;
895 static int lastFlipView = 0;
896 static int lastBoardValid[2] = {0, 0};
897 static Board lastBoard[2];
898 static char lastMarker[BOARD_RANKS][BOARD_FILES];
900 int nr = twoBoards*partnerUp;
902 if(DrawSeekGraph()) return; // [HGM] seekgraph: suppress any drawing if seek graph up
905 if (!lastBoardValid[nr]) return;
906 board = lastBoard[nr];
908 if (!lastBoardValid[nr] || (nr == 0 && lastFlipView != flipView)) {
909 MarkMenuItem("View.Flip View", flipView);
912 if(nr) { SlavePopUp(); SwitchWindow(); } // [HGM] popup board if not yet popped up, and switch drawing to it.
915 * It would be simpler to clear the window with XClearWindow()
916 * but this causes a very distracting flicker.
919 if (!repaint && lastBoardValid[nr] && (nr == 1 || lastFlipView == flipView)) {
921 // if ( lineGap && IsDrawArrowEnabled())
924 /* If too much changes (begin observing new game, etc.), don't
926 do_flash = too_many_diffs(board, lastBoard[nr]) ? 0 : 1;
928 /* Special check for castling so we don't flash both the king
929 and the rook (just flash the king). */
931 if (check_castle_draw(board, lastBoard[nr], &rrow, &rcol)) {
932 /* Mark rook for drawing with NO flashing. */
933 damage[nr][rrow][rcol] |= 1;
937 /* First pass -- Draw (newly) empty squares and repair damage.
938 This prevents you from having a piece show up twice while it
939 is flashing on its new square */
940 for (i = 0; i < BOARD_HEIGHT; i++)
941 for (j = 0; j < BOARD_WIDTH; j++)
942 if (((board[i][j] != lastBoard[nr][i][j] || !nr && marker[i][j] != lastMarker[i][j]) && board[i][j] == EmptySquare)
943 || damage[nr][i][j]) {
944 DrawSquare(i, j, board[i][j], 0);
945 if(damage[nr][i][j] & 2) {
946 drawHighlight(j, i, 0); // repair arrow damage
947 damage[nr][i][j] = False; // this flushed the square as well
948 } else damage[nr][i][j] = 1; // mark for expose
951 /* Second pass -- Draw piece(s) in new position and flash them */
952 for (i = 0; i < BOARD_HEIGHT; i++)
953 for (j = 0; j < BOARD_WIDTH; j++)
954 if (board[i][j] != lastBoard[nr][i][j] || !nr && marker[i][j] != lastMarker[i][j]) {
955 DrawSquare(i, j, board[i][j], do_flash);
956 damage[nr][i][j] = 1; // mark for expose
962 for (i = 0; i < BOARD_HEIGHT; i++)
963 for (j = 0; j < BOARD_WIDTH; j++) {
964 DrawSquare(i, j, board[i][j], 0);
965 damage[nr][i][j] = False;
971 CopyBoard(lastBoard[nr], board);
972 lastBoardValid[nr] = 1;
973 if(nr == 0) { // [HGM] dual: no highlights on second board yet
974 lastFlipView = flipView;
975 for (i = 0; i < BOARD_HEIGHT; i++)
976 for (j = 0; j < BOARD_WIDTH; j++)
977 lastMarker[i][j] = marker[i][j];
979 /* Draw highlights */
980 if (pm1X >= 0 && pm1Y >= 0) {
981 drawHighlight(pm1X, pm1Y, 2);
982 damage[nr][pm1Y][pm1X] = False;
984 if (pm2X >= 0 && pm2Y >= 0) {
985 drawHighlight(pm2X, pm2Y, 2);
986 damage[nr][pm2Y][pm2X] = False;
988 if (hi1X >= 0 && hi1Y >= 0) {
989 drawHighlight(hi1X, hi1Y, 1);
990 damage[nr][hi1Y][hi1X] = False;
992 if (hi2X >= 0 && hi2Y >= 0) {
993 drawHighlight(hi2X, hi2Y, 1);
994 damage[nr][hi2Y][hi2X] = False;
996 DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y);
998 else DrawArrowHighlight (board[EP_STATUS-3], board[EP_STATUS-4], board[EP_STATUS-1], board[EP_STATUS-2]);
1000 /* If piece being dragged around board, must redraw that too */
1004 GraphExpose(currBoard, 0, 0, BOARD_WIDTH*(squareSize + lineGap) + lineGap, BOARD_HEIGHT*(squareSize + lineGap) + lineGap);
1006 for (i = 0; i < BOARD_HEIGHT; i++)
1007 for (j = 0; j < BOARD_WIDTH; j++)
1008 if(damage[nr][i][j]) {
1011 x = lineGap + ((BOARD_WIDTH-1)-j) *
1012 (squareSize + lineGap);
1013 y = lineGap + i * (squareSize + lineGap);
1015 x = lineGap + j * (squareSize + lineGap);
1016 y = lineGap + ((BOARD_HEIGHT-1)-i) *
1017 (squareSize + lineGap);
1019 if(damage[nr][i][j] & 2) // damage by old or new arrow
1020 GraphExpose(currBoard, x - lineGap, y - lineGap, squareSize + 2*lineGap, squareSize + 2*lineGap);
1022 GraphExpose(currBoard, x, y, squareSize, squareSize);
1023 damage[nr][i][j] &= ~2; // remember damage by newly drawn error in '2' bit, to schedule it for erasure next draw
1027 FlashDelay(0); // this flushes drawing queue;
1028 if(nr) SwitchWindow();
1031 /* [AS] Arrow highlighting support */
1033 static double A_WIDTH = 5; /* Width of arrow body */
1035 #define A_HEIGHT_FACTOR 6 /* Length of arrow "point", relative to body width */
1036 #define A_WIDTH_FACTOR 3 /* Width of arrow "point", relative to body width */
1047 return (int) (x + 0.5);
1051 SquareToPos (int rank, int file, int *x, int *y)
1054 *x = lineGap + ((BOARD_WIDTH-1)-file) * (squareSize + lineGap);
1055 *y = lineGap + rank * (squareSize + lineGap);
1057 *x = lineGap + file * (squareSize + lineGap);
1058 *y = lineGap + ((BOARD_HEIGHT-1)-rank) * (squareSize + lineGap);
1062 /* Draw an arrow between two points using current settings */
1064 DrawArrowBetweenPoints (int s_x, int s_y, int d_x, int d_y)
1067 double dx, dy, j, k, x, y;
1070 int h = (d_y > s_y) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1072 arrow[0].x = s_x + A_WIDTH + 0.5;
1075 arrow[1].x = s_x + A_WIDTH + 0.5;
1076 arrow[1].y = d_y - h;
1078 arrow[2].x = arrow[1].x + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1079 arrow[2].y = d_y - h;
1084 arrow[5].x = arrow[1].x - 2*A_WIDTH + 0.5;
1085 arrow[5].y = d_y - h;
1087 arrow[4].x = arrow[5].x - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1088 arrow[4].y = d_y - h;
1090 arrow[6].x = arrow[1].x - 2*A_WIDTH + 0.5;
1093 else if( d_y == s_y ) {
1094 int w = (d_x > s_x) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1097 arrow[0].y = s_y + A_WIDTH + 0.5;
1099 arrow[1].x = d_x - w;
1100 arrow[1].y = s_y + A_WIDTH + 0.5;
1102 arrow[2].x = d_x - w;
1103 arrow[2].y = arrow[1].y + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1108 arrow[5].x = d_x - w;
1109 arrow[5].y = arrow[1].y - 2*A_WIDTH + 0.5;
1111 arrow[4].x = d_x - w;
1112 arrow[4].y = arrow[5].y - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1115 arrow[6].y = arrow[1].y - 2*A_WIDTH + 0.5;
1118 /* [AS] Needed a lot of paper for this! :-) */
1119 dy = (double) (d_y - s_y) / (double) (d_x - s_x);
1120 dx = (double) (s_x - d_x) / (double) (s_y - d_y);
1122 j = sqrt( Sqr(A_WIDTH) / (1.0 + Sqr(dx)) );
1124 k = sqrt( Sqr(A_WIDTH*A_HEIGHT_FACTOR) / (1.0 + Sqr(dy)) );
1129 arrow[0].x = Round(x - j);
1130 arrow[0].y = Round(y + j*dx);
1132 arrow[1].x = Round(arrow[0].x + 2*j); // [HGM] prevent width to be affected by rounding twice
1133 arrow[1].y = Round(arrow[0].y - 2*j*dx);
1136 x = (double) d_x - k;
1137 y = (double) d_y - k*dy;
1140 x = (double) d_x + k;
1141 y = (double) d_y + k*dy;
1144 x = Round(x); y = Round(y); // [HGM] make sure width of shaft is rounded the same way on both ends
1146 arrow[6].x = Round(x - j);
1147 arrow[6].y = Round(y + j*dx);
1149 arrow[2].x = Round(arrow[6].x + 2*j);
1150 arrow[2].y = Round(arrow[6].y - 2*j*dx);
1152 arrow[3].x = Round(arrow[2].x + j*(A_WIDTH_FACTOR-1));
1153 arrow[3].y = Round(arrow[2].y - j*(A_WIDTH_FACTOR-1)*dx);
1158 arrow[5].x = Round(arrow[6].x - j*(A_WIDTH_FACTOR-1));
1159 arrow[5].y = Round(arrow[6].y + j*(A_WIDTH_FACTOR-1)*dx);
1162 DrawPolygon(arrow, 7);
1163 // Polygon( hdc, arrow, 7 );
1167 ArrowDamage (int s_col, int s_row, int d_col, int d_row)
1169 int hor, vert, i, n = partnerUp * twoBoards;
1170 hor = 64*s_col + 32; vert = 64*s_row + 32;
1171 for(i=0; i<= 64; i++) {
1172 damage[n][vert+6>>6][hor+6>>6] |= 2;
1173 damage[n][vert-6>>6][hor+6>>6] |= 2;
1174 damage[n][vert+6>>6][hor-6>>6] |= 2;
1175 damage[n][vert-6>>6][hor-6>>6] |= 2;
1176 hor += d_col - s_col; vert += d_row - s_row;
1180 /* [AS] Draw an arrow between two squares */
1182 DrawArrowBetweenSquares (int s_col, int s_row, int d_col, int d_row)
1184 int s_x, s_y, d_x, d_y;
1186 if( s_col == d_col && s_row == d_row ) {
1190 /* Get source and destination points */
1191 SquareToPos( s_row, s_col, &s_x, &s_y);
1192 SquareToPos( d_row, d_col, &d_x, &d_y);
1195 d_y += squareSize / 2 - squareSize / 4; // [HGM] round towards same centers on all sides!
1197 else if( d_y < s_y ) {
1198 d_y += squareSize / 2 + squareSize / 4;
1201 d_y += squareSize / 2;
1205 d_x += squareSize / 2 - squareSize / 4;
1207 else if( d_x < s_x ) {
1208 d_x += squareSize / 2 + squareSize / 4;
1211 d_x += squareSize / 2;
1214 s_x += squareSize / 2;
1215 s_y += squareSize / 2;
1218 A_WIDTH = squareSize / 14.; //[HGM] make float
1220 DrawArrowBetweenPoints( s_x, s_y, d_x, d_y );
1221 ArrowDamage(s_col, s_row, d_col, d_row);
1225 IsDrawArrowEnabled ()
1227 return (appData.highlightMoveWithArrow || twoBoards && partnerUp) && squareSize >= 32;
1231 DrawArrowHighlight (int fromX, int fromY, int toX,int toY)
1233 if( IsDrawArrowEnabled() && fromX >= 0 && fromY >= 0 && toX >= 0 && toY >= 0)
1234 DrawArrowBetweenSquares(fromX, fromY, toX, toY);