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, lineGap & 1); // pass whether lineGap is odd
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;
588 /* Main control logic for deciding what to animate and how */
591 AnimateMove (Board board, int fromX, int fromY, int toX, int toY)
595 Pnt start, finish, mid;
596 Pnt frames[kFactor * 2 + 1];
597 int nFrames, startColor, endColor;
599 /* Are we animating? */
600 if (!appData.animate || appData.blindfold)
603 if(board[toY][toX] == WhiteRook && board[fromY][fromX] == WhiteKing ||
604 board[toY][toX] == BlackRook && board[fromY][fromX] == BlackKing)
605 return; // [HGM] FRC: no animtion of FRC castlings, as to-square is not true to-square
607 if (fromY < 0 || fromX < 0 || toX < 0 || toY < 0) return;
608 piece = board[fromY][fromX];
609 if (piece >= EmptySquare) return;
614 hop = abs(fromX-toX) == 1 && abs(fromY-toY) == 2 || abs(fromX-toX) == 2 && abs(fromY-toY) == 1;
617 ScreenSquare(fromX, fromY, &start, &startColor);
618 ScreenSquare(toX, toY, &finish, &endColor);
621 /* Knight: make straight movement then diagonal */
622 if (abs(toY - fromY) < abs(toX - fromX)) {
623 mid.x = start.x + (finish.x - start.x) / 2;
627 mid.y = start.y + (finish.y - start.y) / 2;
630 mid.x = start.x + (finish.x - start.x) / 2;
631 mid.y = start.y + (finish.y - start.y) / 2;
634 /* Don't use as many frames for very short moves */
635 if (abs(toY - fromY) + abs(toX - fromX) <= 2)
636 Tween(&start, &mid, &finish, kFactor - 1, frames, &nFrames);
638 Tween(&start, &mid, &finish, kFactor, frames, &nFrames);
639 FrameSequence(Game, piece, startColor, &start, &finish, frames, nFrames);
640 if(Explode(board, fromX, fromY, toX, toY)) { // mark as damaged
642 for(i=0; i<BOARD_WIDTH; i++) for(j=0; j<BOARD_HEIGHT; j++)
643 if((i-toX)*(i-toX) + (j-toY)*(j-toY) < 6) damage[0][j][i] |= 1 + ((i-toX ^ j-toY) & 1);
646 /* Be sure end square is redrawn */
647 damage[0][toY][toX] |= True;
651 ChangeDragPiece (ChessSquare piece)
653 anims[Player].dragPiece = piece;
654 SetDragPiece(Player, piece);
658 DragPieceMove (int x, int y)
662 /* Are we animating? */
663 if (!appData.animateDragging || appData.blindfold)
667 if (! anims[Player].dragActive)
669 /* Move piece, maintaining same relative position
670 of mouse within square */
671 corner.x = x - anims[Player].mouseDelta.x;
672 corner.y = y - anims[Player].mouseDelta.y;
673 AnimationFrame(Player, &corner, anims[Player].dragPiece);
675 if (appData.highlightDragging) {
677 BoardSquare(x, y, &boardX, &boardY);
678 SetHighlights(fromX, fromY, boardX, boardY);
684 DragPieceEnd (int x, int y)
686 int boardX, boardY, color;
689 /* Are we animating? */
690 if (!appData.animateDragging || appData.blindfold)
694 if (! anims[Player].dragActive)
696 /* Last frame in sequence is square piece is
697 placed on, which may not match mouse exactly. */
698 BoardSquare(x, y, &boardX, &boardY);
699 ScreenSquare(boardX, boardY, &corner, &color);
700 EndAnimation(Player, &corner);
702 /* Be sure end square is redrawn */
703 damage[0][boardY][boardX] = True;
705 /* This prevents weird things happening with fast successive
706 clicks which on my Sun at least can cause motion events
707 without corresponding press/release. */
708 anims[Player].dragActive = False;
712 DragPieceBegin (int x, int y, Boolean instantly)
714 int boardX, boardY, color;
717 /* Are we animating? */
718 if (!appData.animateDragging || appData.blindfold)
721 /* Figure out which square we start in and the
722 mouse position relative to top left corner. */
723 BoardSquare(x, y, &boardX, &boardY);
724 anims[Player].startBoardX = boardX;
725 anims[Player].startBoardY = boardY;
726 ScreenSquare(boardX, boardY, &corner, &color);
727 anims[Player].startSquare = corner;
728 anims[Player].startColor = color;
729 /* As soon as we start dragging, the piece will jump slightly to
730 be centered over the mouse pointer. */
731 anims[Player].mouseDelta.x = squareSize/2;
732 anims[Player].mouseDelta.y = squareSize/2;
733 /* Initialise animation */
734 anims[Player].dragPiece = PieceForSquare(boardX, boardY);
736 if (anims[Player].dragPiece >= 0 && anims[Player].dragPiece < EmptySquare) {
737 ChessSquare bgPiece = EmptySquare;
738 anims[Player].dragActive = True;
739 if(boardX == BOARD_RGHT+1 && PieceForSquare(boardX-1, boardY) > 1 ||
740 boardX == BOARD_LEFT-2 && PieceForSquare(boardX+1, boardY) > 1)
741 bgPiece = anims[Player].dragPiece;
742 if(gatingPiece != EmptySquare) bgPiece = gatingPiece;
743 BeginAnimation(Player, anims[Player].dragPiece, bgPiece, color, &corner);
744 /* Mark this square as needing to be redrawn. Note that
745 we don't remove the piece though, since logically (ie
746 as seen by opponent) the move hasn't been made yet. */
747 damage[0][boardY][boardX] = True;
749 anims[Player].dragActive = False;
753 /* Handle expose event while piece being dragged */
758 if (!anims[Player].dragActive || appData.blindfold)
761 /* What we're doing: logically, the move hasn't been made yet,
762 so the piece is still in it's original square. But visually
763 it's being dragged around the board. So we erase the square
764 that the piece is on and draw it at the last known drag point. */
765 DrawOneSquare(anims[Player].startSquare.x, anims[Player].startSquare.y,
766 EmptySquare, anims[Player].startColor, 0, NULL, 0);
767 AnimationFrame(Player, &anims[Player].prevFrame, anims[Player].dragPiece);
768 damage[0][anims[Player].startBoardY][anims[Player].startBoardX] = TRUE;
772 DrawSquare (int row, int column, ChessSquare piece, int do_flash)
774 int square_color, x, y, align=0;
779 /* Calculate delay in milliseconds (2-delays per complete flash) */
780 flash_delay = 500 / appData.flashRate;
783 x = lineGap + ((BOARD_WIDTH-1)-column) *
784 (squareSize + lineGap);
785 y = lineGap + row * (squareSize + lineGap);
787 x = lineGap + column * (squareSize + lineGap);
788 y = lineGap + ((BOARD_HEIGHT-1)-row) *
789 (squareSize + lineGap);
792 square_color = SquareColor(row, column);
794 string[1] = NULLCHAR;
795 if (appData.showCoords && row == (flipView ? BOARD_HEIGHT-1 : 0)
796 && column >= BOARD_LEFT && column < BOARD_RGHT) {
797 string[0] = 'a' + column - BOARD_LEFT;
798 align = 1; // coord in lower-right corner
800 if (appData.showCoords && column == (flipView ? BOARD_RGHT-1 : BOARD_LEFT)) {
801 string[0] = ONE + row;
802 align = 2; // coord in upper-left corner
804 if (column == (flipView ? BOARD_LEFT-1 : BOARD_RGHT) && piece > 1 ) {
805 string[0] = '0' + piece;
806 align = 3; // holdings count in upper-right corner
808 if (column == (flipView ? BOARD_RGHT : BOARD_LEFT-1) && piece > 1) {
809 string[0] = '0' + piece;
810 align = 4; // holdings count in upper-left corner
812 if(square_color == 2 || appData.blindfold) piece = EmptySquare;
814 if (do_flash && piece != EmptySquare && appData.flashCount > 0) {
815 for (i=0; i<appData.flashCount; ++i) {
816 DrawOneSquare(x, y, piece, square_color, 0, string, 0);
817 GraphExpose(currBoard, x, y, squareSize, squareSize);
818 FlashDelay(flash_delay);
819 DrawOneSquare(x, y, EmptySquare, square_color, 0, string, 0);
820 GraphExpose(currBoard, x, y, squareSize, squareSize);
821 FlashDelay(flash_delay);
824 DrawOneSquare(x, y, piece, square_color, partnerUp ? 0 : marker[row][column], string, align);
827 /* Returns 1 if there are "too many" differences between b1 and b2
828 (i.e. more than 1 move was made) */
830 too_many_diffs (Board b1, Board b2)
835 for (i=0; i<BOARD_HEIGHT; ++i) {
836 for (j=0; j<BOARD_WIDTH; ++j) {
837 if (b1[i][j] != b2[i][j]) {
838 if (++c > 4) /* Castling causes 4 diffs */
846 /* Matrix describing castling maneuvers */
847 /* Row, ColRookFrom, ColKingFrom, ColRookTo, ColKingTo */
848 static int castling_matrix[4][5] = {
849 { 0, 0, 4, 3, 2 }, /* 0-0-0, white */
850 { 0, 7, 4, 5, 6 }, /* 0-0, white */
851 { 7, 0, 4, 3, 2 }, /* 0-0-0, black */
852 { 7, 7, 4, 5, 6 } /* 0-0, black */
855 /* Checks whether castling occurred. If it did, *rrow and *rcol
856 are set to the destination (row,col) of the rook that moved.
858 Returns 1 if castling occurred, 0 if not.
860 Note: Only handles a max of 1 castling move, so be sure
861 to call too_many_diffs() first.
864 check_castle_draw (Board newb, Board oldb, int *rrow, int *rcol)
869 /* For each type of castling... */
870 for (i=0; i<4; ++i) {
871 r = castling_matrix[i];
873 /* Check the 4 squares involved in the castling move */
875 for (j=1; j<=4; ++j) {
876 if (newb[r[0]][r[j]] == oldb[r[0]][r[j]]) {
883 /* All 4 changed, so it must be a castling move */
893 DrawPosition (int repaint, Board board)
895 int i, j, do_flash, exposeAll = False;
896 static int lastFlipView = 0;
897 static int lastBoardValid[2] = {0, 0};
898 static Board lastBoard[2];
899 static char lastMarker[BOARD_RANKS][BOARD_FILES];
901 int nr = twoBoards*partnerUp;
903 if(DrawSeekGraph()) return; // [HGM] seekgraph: suppress any drawing if seek graph up
906 if (!lastBoardValid[nr]) return;
907 board = lastBoard[nr];
909 if (!lastBoardValid[nr] || (nr == 0 && lastFlipView != flipView)) {
910 MarkMenuItem("View.Flip View", flipView);
913 if(nr) { SlavePopUp(); SwitchWindow(); } // [HGM] popup board if not yet popped up, and switch drawing to it.
916 * It would be simpler to clear the window with XClearWindow()
917 * but this causes a very distracting flicker.
920 if (!repaint && lastBoardValid[nr] && (nr == 1 || lastFlipView == flipView)) {
922 // if ( lineGap && IsDrawArrowEnabled())
925 /* If too much changes (begin observing new game, etc.), don't
927 do_flash = too_many_diffs(board, lastBoard[nr]) ? 0 : 1;
929 /* Special check for castling so we don't flash both the king
930 and the rook (just flash the king). */
932 if (check_castle_draw(board, lastBoard[nr], &rrow, &rcol)) {
933 /* Mark rook for drawing with NO flashing. */
934 damage[nr][rrow][rcol] |= 1;
938 /* First pass -- Draw (newly) empty squares and repair damage.
939 This prevents you from having a piece show up twice while it
940 is flashing on its new square */
941 for (i = 0; i < BOARD_HEIGHT; i++)
942 for (j = 0; j < BOARD_WIDTH; j++)
943 if (((board[i][j] != lastBoard[nr][i][j] || !nr && marker[i][j] != lastMarker[i][j]) && board[i][j] == EmptySquare)
944 || damage[nr][i][j]) {
945 DrawSquare(i, j, board[i][j], 0);
946 if(damage[nr][i][j] & 2) {
947 drawHighlight(j, i, 0); // repair arrow damage
948 damage[nr][i][j] = False; // this flushed the square as well
949 } else damage[nr][i][j] = 1; // mark for expose
952 /* Second pass -- Draw piece(s) in new position and flash them */
953 for (i = 0; i < BOARD_HEIGHT; i++)
954 for (j = 0; j < BOARD_WIDTH; j++)
955 if (board[i][j] != lastBoard[nr][i][j] || !nr && marker[i][j] != lastMarker[i][j]) {
956 DrawSquare(i, j, board[i][j], do_flash);
957 damage[nr][i][j] = 1; // mark for expose
963 for (i = 0; i < BOARD_HEIGHT; i++)
964 for (j = 0; j < BOARD_WIDTH; j++) {
965 DrawSquare(i, j, board[i][j], 0);
966 damage[nr][i][j] = False;
972 CopyBoard(lastBoard[nr], board);
973 lastBoardValid[nr] = 1;
974 if(nr == 0) { // [HGM] dual: no highlights on second board yet
975 lastFlipView = flipView;
976 for (i = 0; i < BOARD_HEIGHT; i++)
977 for (j = 0; j < BOARD_WIDTH; j++)
978 lastMarker[i][j] = marker[i][j];
980 /* Draw highlights */
981 if (pm1X >= 0 && pm1Y >= 0) {
982 drawHighlight(pm1X, pm1Y, 2);
983 damage[nr][pm1Y][pm1X] = False;
985 if (pm2X >= 0 && pm2Y >= 0) {
986 drawHighlight(pm2X, pm2Y, 2);
987 damage[nr][pm2Y][pm2X] = False;
989 if (hi1X >= 0 && hi1Y >= 0) {
990 drawHighlight(hi1X, hi1Y, 1);
991 damage[nr][hi1Y][hi1X] = False;
993 if (hi2X >= 0 && hi2Y >= 0) {
994 drawHighlight(hi2X, hi2Y, 1);
995 damage[nr][hi2Y][hi2X] = False;
997 DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y);
999 else DrawArrowHighlight (board[EP_STATUS-3], board[EP_STATUS-4], board[EP_STATUS-1], board[EP_STATUS-2]);
1001 /* If piece being dragged around board, must redraw that too */
1005 GraphExpose(currBoard, 0, 0, BOARD_WIDTH*(squareSize + lineGap) + lineGap, BOARD_HEIGHT*(squareSize + lineGap) + lineGap);
1007 for (i = 0; i < BOARD_HEIGHT; i++)
1008 for (j = 0; j < BOARD_WIDTH; j++)
1009 if(damage[nr][i][j]) {
1012 x = lineGap + ((BOARD_WIDTH-1)-j) *
1013 (squareSize + lineGap);
1014 y = lineGap + i * (squareSize + lineGap);
1016 x = lineGap + j * (squareSize + lineGap);
1017 y = lineGap + ((BOARD_HEIGHT-1)-i) *
1018 (squareSize + lineGap);
1020 if(damage[nr][i][j] & 2) // damage by old or new arrow
1021 GraphExpose(currBoard, x - lineGap, y - lineGap, squareSize + 2*lineGap, squareSize + 2*lineGap);
1023 GraphExpose(currBoard, x, y, squareSize, squareSize);
1024 damage[nr][i][j] &= ~2; // remember damage by newly drawn error in '2' bit, to schedule it for erasure next draw
1028 FlashDelay(0); // this flushes drawing queue;
1029 if(nr) SwitchWindow();
1032 /* [AS] Arrow highlighting support */
1034 static double A_WIDTH = 5; /* Width of arrow body */
1036 #define A_HEIGHT_FACTOR 6 /* Length of arrow "point", relative to body width */
1037 #define A_WIDTH_FACTOR 3 /* Width of arrow "point", relative to body width */
1048 return (int) (x + 0.5);
1052 SquareToPos (int rank, int file, int *x, int *y)
1055 *x = lineGap + ((BOARD_WIDTH-1)-file) * (squareSize + lineGap);
1056 *y = lineGap + rank * (squareSize + lineGap);
1058 *x = lineGap + file * (squareSize + lineGap);
1059 *y = lineGap + ((BOARD_HEIGHT-1)-rank) * (squareSize + lineGap);
1063 /* Draw an arrow between two points using current settings */
1065 DrawArrowBetweenPoints (int s_x, int s_y, int d_x, int d_y)
1068 double dx, dy, j, k, x, y;
1071 int h = (d_y > s_y) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1073 arrow[0].x = s_x + A_WIDTH + 0.5;
1076 arrow[1].x = s_x + A_WIDTH + 0.5;
1077 arrow[1].y = d_y - h;
1079 arrow[2].x = arrow[1].x + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1080 arrow[2].y = d_y - h;
1085 arrow[5].x = arrow[1].x - 2*A_WIDTH + 0.5;
1086 arrow[5].y = d_y - h;
1088 arrow[4].x = arrow[5].x - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1089 arrow[4].y = d_y - h;
1091 arrow[6].x = arrow[1].x - 2*A_WIDTH + 0.5;
1094 else if( d_y == s_y ) {
1095 int w = (d_x > s_x) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1098 arrow[0].y = s_y + A_WIDTH + 0.5;
1100 arrow[1].x = d_x - w;
1101 arrow[1].y = s_y + A_WIDTH + 0.5;
1103 arrow[2].x = d_x - w;
1104 arrow[2].y = arrow[1].y + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1109 arrow[5].x = d_x - w;
1110 arrow[5].y = arrow[1].y - 2*A_WIDTH + 0.5;
1112 arrow[4].x = d_x - w;
1113 arrow[4].y = arrow[5].y - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1116 arrow[6].y = arrow[1].y - 2*A_WIDTH + 0.5;
1119 /* [AS] Needed a lot of paper for this! :-) */
1120 dy = (double) (d_y - s_y) / (double) (d_x - s_x);
1121 dx = (double) (s_x - d_x) / (double) (s_y - d_y);
1123 j = sqrt( Sqr(A_WIDTH) / (1.0 + Sqr(dx)) );
1125 k = sqrt( Sqr(A_WIDTH*A_HEIGHT_FACTOR) / (1.0 + Sqr(dy)) );
1130 arrow[0].x = Round(x - j);
1131 arrow[0].y = Round(y + j*dx);
1133 arrow[1].x = Round(arrow[0].x + 2*j); // [HGM] prevent width to be affected by rounding twice
1134 arrow[1].y = Round(arrow[0].y - 2*j*dx);
1137 x = (double) d_x - k;
1138 y = (double) d_y - k*dy;
1141 x = (double) d_x + k;
1142 y = (double) d_y + k*dy;
1145 x = Round(x); y = Round(y); // [HGM] make sure width of shaft is rounded the same way on both ends
1147 arrow[6].x = Round(x - j);
1148 arrow[6].y = Round(y + j*dx);
1150 arrow[2].x = Round(arrow[6].x + 2*j);
1151 arrow[2].y = Round(arrow[6].y - 2*j*dx);
1153 arrow[3].x = Round(arrow[2].x + j*(A_WIDTH_FACTOR-1));
1154 arrow[3].y = Round(arrow[2].y - j*(A_WIDTH_FACTOR-1)*dx);
1159 arrow[5].x = Round(arrow[6].x - j*(A_WIDTH_FACTOR-1));
1160 arrow[5].y = Round(arrow[6].y + j*(A_WIDTH_FACTOR-1)*dx);
1163 DrawPolygon(arrow, 7);
1164 // Polygon( hdc, arrow, 7 );
1168 ArrowDamage (int s_col, int s_row, int d_col, int d_row)
1170 int hor, vert, i, n = partnerUp * twoBoards;
1171 hor = 64*s_col + 32; vert = 64*s_row + 32;
1172 for(i=0; i<= 64; i++) {
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 damage[n][vert-6>>6][hor-6>>6] |= 2;
1177 hor += d_col - s_col; vert += d_row - s_row;
1181 /* [AS] Draw an arrow between two squares */
1183 DrawArrowBetweenSquares (int s_col, int s_row, int d_col, int d_row)
1185 int s_x, s_y, d_x, d_y;
1187 if( s_col == d_col && s_row == d_row ) {
1191 /* Get source and destination points */
1192 SquareToPos( s_row, s_col, &s_x, &s_y);
1193 SquareToPos( d_row, d_col, &d_x, &d_y);
1196 d_y += squareSize / 2 - squareSize / 4; // [HGM] round towards same centers on all sides!
1198 else if( d_y < s_y ) {
1199 d_y += squareSize / 2 + squareSize / 4;
1202 d_y += squareSize / 2;
1206 d_x += squareSize / 2 - squareSize / 4;
1208 else if( d_x < s_x ) {
1209 d_x += squareSize / 2 + squareSize / 4;
1212 d_x += squareSize / 2;
1215 s_x += squareSize / 2;
1216 s_y += squareSize / 2;
1219 A_WIDTH = squareSize / 14.; //[HGM] make float
1221 DrawArrowBetweenPoints( s_x, s_y, d_x, d_y );
1222 ArrowDamage(s_col, s_row, d_col, d_row);
1226 IsDrawArrowEnabled ()
1228 return (appData.highlightMoveWithArrow || twoBoards && partnerUp) && squareSize >= 32;
1232 DrawArrowHighlight (int fromX, int fromY, int toX,int toY)
1234 if( IsDrawArrowEnabled() && fromX >= 0 && fromY >= 0 && toX >= 0 && toY >= 0)
1235 DrawArrowBetweenSquares(fromX, fromY, toX, toY);