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, 2013, 2014, 2015, 2016 Free
9 * Software Foundation, Inc.
11 * The following terms apply to Digital Equipment Corporation's copyright
13 * ------------------------------------------------------------------------
16 * Permission to use, copy, modify, and distribute this software and its
17 * documentation for any purpose and without fee is hereby granted,
18 * provided that the above copyright notice appear in all copies and that
19 * both that copyright notice and this permission notice appear in
20 * supporting documentation, and that the name of Digital not be
21 * used in advertising or publicity pertaining to distribution of the
22 * software without specific, written prior permission.
24 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
25 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
26 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
27 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
28 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
29 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31 * ------------------------------------------------------------------------
33 * The following terms apply to the enhanced version of XBoard
34 * distributed by the Free Software Foundation:
35 * ------------------------------------------------------------------------
37 * GNU XBoard is free software: you can redistribute it and/or modify
38 * it under the terms of the GNU General Public License as published by
39 * the Free Software Foundation, either version 3 of the License, or (at
40 * your option) any later version.
42 * GNU XBoard is distributed in the hope that it will be useful, but
43 * WITHOUT ANY WARRANTY; without even the implied warranty of
44 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
45 * General Public License for more details.
47 * You should have received a copy of the GNU General Public License
48 * along with this program. If not, see http://www.gnu.org/licenses/. *
50 *------------------------------------------------------------------------
51 ** See the file ChangeLog for a revision history. */
61 #include <sys/types.h>
69 #else /* not STDC_HEADERS */
70 extern char *getenv();
73 # else /* not HAVE_STRING_H */
75 # endif /* not HAVE_STRING_H */
76 #endif /* not STDC_HEADERS */
78 #if TIME_WITH_SYS_TIME
79 # include <sys/time.h>
83 # include <sys/time.h>
94 # include <sys/wait.h>
110 #define usleep(t) _sleep2(((t)+500)/1000)
114 int squareSize, lineGap;
116 int damage[2][BOARD_RANKS][BOARD_FILES];
118 /* There can be two pieces being animated at once: a player
119 can begin dragging a piece before the remote opponent has moved. */
121 AnimState anims[NrOfAnims];
123 static void DrawSquare P((int row, int column, ChessSquare piece, int do_flash));
124 static Boolean IsDrawArrowEnabled P((void));
125 static void DrawArrowHighlight P((int fromX, int fromY, int toX,int toY));
126 static void ArrowDamage P((int s_col, int s_row, int d_col, int d_row));
129 drawHighlight (int file, int rank, int type)
133 if (lineGap == 0) return;
136 x = lineGap/2 + ((BOARD_WIDTH-1)-file) *
137 (squareSize + lineGap);
138 y = lineGap/2 + rank * (squareSize + lineGap);
140 x = lineGap/2 + file * (squareSize + lineGap);
141 y = lineGap/2 + ((BOARD_HEIGHT-1)-rank) *
142 (squareSize + lineGap);
145 DrawBorder(x,y, type, lineGap & 1); // pass whether lineGap is odd
148 int hi1X = -1, hi1Y = -1, hi2X = -1, hi2Y = -1;
149 int pm1X = -1, pm1Y = -1, pm2X = -1, pm2Y = -1;
152 SetHighlights (int fromX, int fromY, int toX, int toY)
153 { // [HGM] schedule old for erasure, and leave drawing new to DrawPosition
156 if (hi1X >= 0 && hi1Y >= 0) {
157 if (hi1X != fromX || hi1Y != fromY) {
158 damage[0][hi1Y][hi1X] |= 2;
164 if (hi2X >= 0 && hi2Y >= 0) {
165 if (hi2X != toX || hi2Y != toY) {
166 damage[0][hi2Y][hi2X] |= 2;
172 if(change > 12 && IsDrawArrowEnabled()) ArrowDamage(hi1X, hi1Y, hi2X, hi2Y);
183 SetHighlights(-1, -1, -1, -1);
188 SetPremoveHighlights (int fromX, int fromY, int toX, int toY)
190 if (pm1X != fromX || pm1Y != fromY) {
191 if (pm1X >= 0 && pm1Y >= 0) {
192 damage[0][pm1Y][pm1X] |= 2;
195 if (pm2X != toX || pm2Y != toY) {
196 if (pm2X >= 0 && pm2Y >= 0) {
197 damage[0][pm1Y][pm1X] |= 2;
207 ClearPremoveHighlights ()
209 SetPremoveHighlights(-1, -1, -1, -1);
213 * If the user selects on a border boundary, return -1; if off the board,
214 * return -2. Otherwise map the event coordinate to the square.
217 EventToSquare (int x, int limit)
224 if ((x % (squareSize + lineGap)) >= squareSize)
226 x /= (squareSize + lineGap);
232 /* [HR] determine square color depending on chess variant. */
234 SquareColor (int row, int column)
238 if (gameInfo.variant == VariantXiangqi) {
239 if (column >= 3 && column <= 5 && row >= 0 && row <= 2) {
241 } else if (column >= 3 && column <= 5 && row >= 7 && row <= 9) {
243 } else if (row <= 4) {
249 square_color = ((column + row) % 2) == 1;
252 /* [hgm] holdings: next line makes all holdings squares light */
253 if(column < BOARD_LEFT || column >= BOARD_RGHT) square_color = 1;
255 if ( // [HGM] holdings: blank out area between board and holdings
256 column == BOARD_LEFT-1
257 || column == BOARD_RGHT
258 || (column == BOARD_LEFT-2 && row < BOARD_HEIGHT-gameInfo.holdingsSize)
259 || (column == BOARD_RGHT+1 && row >= gameInfo.holdingsSize) )
260 square_color = 2; // black
265 /* Convert board position to corner of screen rect and color */
268 ScreenSquare (int column, int row, Pnt *pt, int *color)
271 pt->x = lineGap + ((BOARD_WIDTH-1)-column) * (squareSize + lineGap);
272 pt->y = lineGap + row * (squareSize + lineGap);
274 pt->x = lineGap + column * (squareSize + lineGap);
275 pt->y = lineGap + ((BOARD_HEIGHT-1)-row) * (squareSize + lineGap);
277 *color = SquareColor(row, column);
280 /* Convert window coords to square */
283 BoardSquare (int x, int y, int *column, int *row)
285 *column = EventToSquare(x, BOARD_WIDTH);
286 if (flipView && *column >= 0)
287 *column = BOARD_WIDTH - 1 - *column;
288 *row = EventToSquare(y, BOARD_HEIGHT);
289 if (!flipView && *row >= 0)
290 *row = BOARD_HEIGHT - 1 - *row;
293 /* Generate a series of frame coords from start->mid->finish.
294 The movement rate doubles until the half way point is
295 reached, then halves back down to the final destination,
296 which gives a nice slow in/out effect. The algorithmn
297 may seem to generate too many intermediates for short
298 moves, but remember that the purpose is to attract the
299 viewers attention to the piece about to be moved and
300 then to where it ends up. Too few frames would be less
304 Tween (Pnt *start, Pnt *mid, Pnt *finish, int factor, Pnt frames[], int *nFrames)
306 int fraction, n, count;
310 /* Slow in, stepping 1/16th, then 1/8th, ... */
312 for (n = 0; n < factor; n++)
314 for (n = 0; n < factor; n++) {
315 frames[count].x = start->x + (mid->x - start->x) / fraction;
316 frames[count].y = start->y + (mid->y - start->y) / fraction;
318 fraction = fraction / 2;
322 frames[count] = *mid;
325 /* Slow out, stepping 1/2, then 1/4, ... */
327 for (n = 0; n < factor; n++) {
328 frames[count].x = finish->x - (finish->x - mid->x) / fraction;
329 frames[count].y = finish->y - (finish->y - mid->y) / fraction;
331 fraction = fraction * 2;
336 /**** Animation code by Hugh Fisher, DCS, ANU.
338 Known problem: if a window overlapping the board is
339 moved away while a piece is being animated underneath,
340 the newly exposed area won't be updated properly.
341 I can live with this.
343 Known problem: if you look carefully at the animation
344 of pieces in mono mode, they are being drawn as solid
345 shapes without interior detail while moving. Fixing
346 this would be a major complication for minimal return.
351 #undef Max /* just in case */
353 #define Max(a, b) ((a) > (b) ? (a) : (b))
354 #define Min(a, b) ((a) < (b) ? (a) : (b))
357 short int x, y, width, height;
367 SetRect (MyRectangle *rect, int x, int y, int width, int height)
372 rect->height = height;
375 /* Test if two frames overlap. If they do, return
376 intersection rect within old and location of
377 that rect within new. */
380 Intersect ( Pnt *old, Pnt *new, int size, MyRectangle *area, Pnt *pt)
382 if (old->x > new->x + size || new->x > old->x + size ||
383 old->y > new->y + size || new->y > old->y + size) {
386 SetRect(area, Max(new->x - old->x, 0), Max(new->y - old->y, 0),
387 size - abs(old->x - new->x), size - abs(old->y - new->y));
388 pt->x = Max(old->x - new->x, 0);
389 pt->y = Max(old->y - new->y, 0);
394 /* For two overlapping frames, return the rect(s)
395 in the old that do not intersect with the new. */
398 CalcUpdateRects (Pnt *old, Pnt *new, int size, MyRectangle update[], int *nUpdates)
402 /* If old = new (shouldn't happen) then nothing to draw */
403 if (old->x == new->x && old->y == new->y) {
407 /* Work out what bits overlap. Since we know the rects
408 are the same size we don't need a full intersect calc. */
410 /* Top or bottom edge? */
411 if (new->y > old->y) {
412 SetRect(&(update[count]), old->x, old->y, size, new->y - old->y);
414 } else if (old->y > new->y) {
415 SetRect(&(update[count]), old->x, old->y + size - (old->y - new->y),
416 size, old->y - new->y);
419 /* Left or right edge - don't overlap any update calculated above. */
420 if (new->x > old->x) {
421 SetRect(&(update[count]), old->x, Max(new->y, old->y),
422 new->x - old->x, size - abs(new->y - old->y));
424 } else if (old->x > new->x) {
425 SetRect(&(update[count]), new->x + size, Max(new->y, old->y),
426 old->x - new->x, size - abs(new->y - old->y));
433 /* Animate the movement of a single piece */
436 BeginAnimation (AnimNr anr, ChessSquare piece, ChessSquare bgPiece, int startColor, Pnt *start)
438 AnimState *anim = &anims[anr];
440 if(appData.upsideDown && flipView) piece += piece < BlackPawn ? BlackPawn : -BlackPawn;
441 /* The old buffer is initialised with the start square (empty) */
442 if(bgPiece == EmptySquare) {
443 DrawBlank(anr, start->x, start->y, startColor);
445 /* Kludge alert: When gating we want the introduced
446 piece to appear on the from square. To generate an
447 image of it, we draw it on the board, copy the image,
448 and draw the original piece again. */
449 if(piece != bgPiece) DrawSquare(anim->startBoardY, anim->startBoardX, bgPiece, 0);
450 CopyRectangle(anr, DISP, 2,
451 start->x, start->y, squareSize, squareSize,
452 0, 0); // [HGM] zh: unstack in stead of grab
453 if(piece != bgPiece) DrawSquare(anim->startBoardY, anim->startBoardX, piece, 0);
455 anim->prevFrame = *start;
457 SetDragPiece(anr, piece);
461 AnimationFrame (AnimNr anr, Pnt *frame, ChessSquare piece)
463 MyRectangle updates[4];
466 AnimState *anim = &anims[anr];
467 int count, i, x, y, w, h;
469 /* Save what we are about to draw into the new buffer */
470 CopyRectangle(anr, DISP, 0,
471 x = frame->x, y = frame->y, w = squareSize, h = squareSize,
474 /* Erase bits of the previous frame */
475 if (Intersect(&anim->prevFrame, frame, squareSize, &overlap, &pt)) {
476 /* Where the new frame overlapped the previous,
477 the contents in newBuf are wrong. */
478 CopyRectangle(anr, 2, 0,
479 overlap.x, overlap.y,
480 overlap.width, overlap.height,
482 /* Repaint the areas in the old that don't overlap new */
483 CalcUpdateRects(&anim->prevFrame, frame, squareSize, updates, &count);
484 for (i = 0; i < count; i++)
485 CopyRectangle(anr, 2, DISP,
486 updates[i].x - anim->prevFrame.x,
487 updates[i].y - anim->prevFrame.y,
488 updates[i].width, updates[i].height,
489 updates[i].x, updates[i].y);
490 /* [HGM] correct expose rectangle to encompass both overlapping squares */
491 if(x > anim->prevFrame.x) w += x - anim->prevFrame.x, x = anim->prevFrame.x;
492 else w += anim->prevFrame.x - x;
493 if(y > anim->prevFrame.y) h += y - anim->prevFrame.y, y = anim->prevFrame.y;
494 else h += anim->prevFrame.y - y;
496 /* Easy when no overlap */
497 CopyRectangle(anr, 2, DISP,
498 0, 0, squareSize, squareSize,
499 anim->prevFrame.x, anim->prevFrame.y);
500 GraphExpose(currBoard, anim->prevFrame.x, anim->prevFrame.y, squareSize, squareSize);
503 /* Save this frame for next time round */
504 CopyRectangle(anr, 0, 2,
505 0, 0, squareSize, squareSize,
507 anim->prevFrame = *frame;
509 /* Draw piece over original screen contents, not current,
510 and copy entire rect. Wipes out overlapping piece images. */
511 InsertPiece(anr, piece);
512 CopyRectangle(anr, 0, DISP,
513 0, 0, squareSize, squareSize,
515 GraphExpose(currBoard, x, y, w, h);
519 EndAnimation (AnimNr anr, Pnt *finish)
521 MyRectangle updates[4];
525 AnimState *anim = &anims[anr];
527 /* The main code will redraw the final square, so we
528 only need to erase the bits that don't overlap. */
529 if (Intersect(&anim->prevFrame, finish, squareSize, &overlap, &pt)) {
530 CalcUpdateRects(&anim->prevFrame, finish, squareSize, updates, &count);
531 for (i = 0; i < count; i++)
532 CopyRectangle(anr, 2, DISP,
533 updates[i].x - anim->prevFrame.x,
534 updates[i].y - anim->prevFrame.y,
535 updates[i].width, updates[i].height,
536 updates[i].x, updates[i].y);
538 CopyRectangle(anr, 2, DISP,
539 0, 0, squareSize, squareSize,
540 anim->prevFrame.x, anim->prevFrame.y);
545 FrameSequence (AnimNr anr, ChessSquare piece, int startColor, Pnt *start, Pnt *finish, Pnt frames[], int nFrames)
549 BeginAnimation(anr, piece, EmptySquare, startColor, start);
550 for (n = 0; n < nFrames; n++) {
551 AnimationFrame(anr, &(frames[n]), piece);
552 FrameDelay(appData.animSpeed);
554 EndAnimation(anr, finish);
558 AnimateAtomicCapture (Board board, int fromX, int fromY, int toX, int toY)
561 ChessSquare piece = board[fromY][toY];
562 board[fromY][toY] = EmptySquare;
563 DrawPosition(FALSE, board);
565 x = lineGap + ((BOARD_WIDTH-1)-toX) * (squareSize + lineGap);
566 y = lineGap + toY * (squareSize + lineGap);
568 x = lineGap + toX * (squareSize + lineGap);
569 y = lineGap + ((BOARD_HEIGHT-1)-toY) * (squareSize + lineGap);
571 for(i=1; i<4*kFactor; i++) {
572 int r = squareSize * 9 * i/(20*kFactor - 5);
573 DrawDot(1, x + squareSize/2 - r, y+squareSize/2 - r, 2*r);
574 FrameDelay(appData.animSpeed);
576 board[fromY][toY] = piece;
580 /* Main control logic for deciding what to animate and how */
583 AnimateMove (Board board, int fromX, int fromY, int toX, int toY)
586 int hop, x = toX, y = toY, x2 = kill2X;
587 Pnt start, finish, mid;
588 Pnt frames[kFactor * 2 + 1];
589 int nFrames, startColor, endColor;
591 if(killX >= 0 && IS_LION(board[fromY][fromX])) Roar();
593 /* Are we animating? */
594 if (!appData.animate || appData.blindfold)
597 if(board[toY][toX] == WhiteRook && board[fromY][fromX] == WhiteKing ||
598 board[toY][toX] == BlackRook && board[fromY][fromX] == BlackKing ||
599 board[toY][toX] == WhiteKing && board[fromY][fromX] == WhiteRook || // [HGM] seirawan
600 board[toY][toX] == BlackKing && board[fromY][fromX] == BlackRook)
601 return; // [HGM] FRC: no animtion of FRC castlings, as to-square is not true to-square
603 if (fromY < 0 || fromX < 0 || toX < 0 || toY < 0) return;
604 piece = board[fromY][fromX];
605 if (piece >= EmptySquare) return;
607 if(x2 >= 0) toX = kill2X, toY = kill2Y; else
608 if(killX >= 0) toX = killX, toY = killY; // [HGM] lion: first to kill square
615 hop = abs(fromX-toX) == 1 && abs(fromY-toY) == 2 || abs(fromX-toX) == 2 && abs(fromY-toY) == 1;
618 ScreenSquare(fromX, fromY, &start, &startColor);
619 ScreenSquare(toX, toY, &finish, &endColor);
622 /* Knight: make straight movement then diagonal */
623 if (abs(toY - fromY) < abs(toX - fromX)) {
624 mid.x = start.x + (finish.x - start.x) / 2;
628 mid.y = start.y + (finish.y - start.y) / 2;
631 mid.x = start.x + (finish.x - start.x) / 2;
632 mid.y = start.y + (finish.y - start.y) / 2;
635 /* Don't use as many frames for very short moves */
636 if (abs(toY - fromY) + abs(toX - fromX) <= 2)
637 Tween(&start, &mid, &finish, kFactor - 1, frames, &nFrames);
639 Tween(&start, &mid, &finish, kFactor, frames, &nFrames);
640 FrameSequence(Game, piece, startColor, &start, &finish, frames, nFrames);
641 if(Explode(board, fromX, fromY, toX, toY)) { // mark as damaged
643 for(i=0; i<BOARD_WIDTH; i++) for(j=0; j<BOARD_HEIGHT; j++)
644 if((i-toX)*(i-toX) + (j-toY)*(j-toY) < 6) damage[0][j][i] |= 1 + ((i-toX ^ j-toY) & 1);
647 /* Be sure end square is redrawn, with piece in it */
648 damage[0][toY][toX] |= 4;
650 if(toX == x2 && toY == kill2Y) { fromX = toX; fromY = toY; toX = killX; toY = killY; x2 = -1; goto again; } // second leg
651 if(toX != x || toY != y) { fromX = toX; fromY = toY; toX = x; toY = y; goto again; } // second leg
655 ChangeDragPiece (ChessSquare piece)
657 anims[Player].dragPiece = piece;
658 SetDragPiece(Player, piece);
659 damage[0][fromY][fromX] = True;
663 DragPieceMove (int x, int y)
667 /* Are we animating? */
668 if (!appData.animateDragging || appData.blindfold)
672 if (! anims[Player].dragActive)
674 /* Move piece, maintaining same relative position
675 of mouse within square */
676 corner.x = x - anims[Player].mouseDelta.x;
677 corner.y = y - anims[Player].mouseDelta.y;
678 AnimationFrame(Player, &corner, anims[Player].dragPiece);
680 if (appData.highlightDragging) {
682 BoardSquare(x, y, &boardX, &boardY);
683 SetHighlights(fromX, fromY, boardX, boardY);
689 DragPieceEnd (int x, int y)
691 int boardX, boardY, color;
694 /* Are we animating? */
695 if (!appData.animateDragging || appData.blindfold)
699 if (! anims[Player].dragActive)
701 /* Last frame in sequence is square piece is
702 placed on, which may not match mouse exactly. */
703 BoardSquare(x, y, &boardX, &boardY);
704 ScreenSquare(boardX, boardY, &corner, &color);
705 EndAnimation(Player, &corner);
707 /* Be sure end square is redrawn */
708 damage[0][boardY][boardX] = True;
710 /* This prevents weird things happening with fast successive
711 clicks which on my Sun at least can cause motion events
712 without corresponding press/release. */
713 anims[Player].dragActive = False;
717 DragPieceBegin (int x, int y, Boolean instantly)
719 int boardX, boardY, color;
722 /* Are we animating? */
723 if (!appData.animateDragging || appData.blindfold)
726 /* Figure out which square we start in and the
727 mouse position relative to top left corner. */
728 BoardSquare(x, y, &boardX, &boardY);
729 anims[Player].startBoardX = boardX;
730 anims[Player].startBoardY = boardY;
731 ScreenSquare(boardX, boardY, &corner, &color);
732 anims[Player].startSquare = corner;
733 anims[Player].startColor = color;
734 /* As soon as we start dragging, the piece will jump slightly to
735 be centered over the mouse pointer. */
736 anims[Player].mouseDelta.x = squareSize/2;
737 anims[Player].mouseDelta.y = squareSize/2;
738 /* Initialise animation */
739 anims[Player].dragPiece = PieceForSquare(boardX, boardY);
741 if (anims[Player].dragPiece >= 0 && anims[Player].dragPiece < EmptySquare) {
742 ChessSquare bgPiece = EmptySquare;
743 anims[Player].dragActive = True;
744 if(boardX == BOARD_RGHT+1 && PieceForSquare(boardX-1, boardY) > 1 ||
745 boardX == BOARD_LEFT-2 && PieceForSquare(boardX+1, boardY) > 1)
746 bgPiece = anims[Player].dragPiece;
747 if(gatingPiece != EmptySquare) bgPiece = gatingPiece;
748 BeginAnimation(Player, anims[Player].dragPiece, bgPiece, color, &corner);
749 /* Mark this square as needing to be redrawn. Note that
750 we don't remove the piece though, since logically (ie
751 as seen by opponent) the move hasn't been made yet. */
752 damage[0][boardY][boardX] = True;
754 anims[Player].dragActive = False;
758 /* Handle expose event while piece being dragged */
763 if (!anims[Player].dragActive || appData.blindfold)
766 /* What we're doing: logically, the move hasn't been made yet,
767 so the piece is still in it's original square. But visually
768 it's being dragged around the board. So we erase the square
769 that the piece is on and draw it at the last known drag point. */
770 DrawOneSquare(anims[Player].startSquare.x, anims[Player].startSquare.y,
771 EmptySquare, anims[Player].startColor, 0, NULL, NULL, 0);
772 AnimationFrame(Player, &anims[Player].prevFrame, anims[Player].dragPiece);
773 damage[0][anims[Player].startBoardY][anims[Player].startBoardX] = TRUE;
777 DrawSquare (int row, int column, ChessSquare piece, int do_flash)
779 int square_color, x, y, align=0;
781 char tString[3], bString[2];
784 /* Calculate delay in milliseconds (2-delays per complete flash) */
785 flash_delay = 500 / appData.flashRate;
788 x = lineGap + ((BOARD_WIDTH-1)-column) *
789 (squareSize + lineGap);
790 y = lineGap + row * (squareSize + lineGap);
792 x = lineGap + column * (squareSize + lineGap);
793 y = lineGap + ((BOARD_HEIGHT-1)-row) *
794 (squareSize + lineGap);
797 square_color = SquareColor(row, column);
799 bString[1] = bString[0] = NULLCHAR;
800 if (appData.showCoords && row == (flipView ? BOARD_HEIGHT-1 : 0)
801 && column >= BOARD_LEFT && column < BOARD_RGHT) {
802 bString[0] = 'a' + column - BOARD_LEFT;
803 align = 1; // coord in lower-right corner
805 if (appData.showCoords && column == (flipView ? BOARD_RGHT-1 : BOARD_LEFT)) {
806 snprintf(tString, 3, "%d", ONE - '0' + row);
807 align = 2; // coord in upper-left corner
809 if (column == (flipView ? BOARD_LEFT-1 : BOARD_RGHT) && piece > 1 ) {
810 snprintf(tString, 3, "%d", piece);
811 align = 3; // holdings count in upper-right corner
813 if (column == (flipView ? BOARD_RGHT : BOARD_LEFT-1) && piece > 1) {
814 snprintf(tString, 3, "%d", piece);
815 align = 4; // holdings count in upper-left corner
817 if(piece == DarkSquare) square_color = 2;
818 if(square_color == 2 || appData.blindfold) piece = EmptySquare;
820 if (do_flash && piece != EmptySquare && appData.flashCount > 0) {
821 for (i=0; i<appData.flashCount; ++i) {
822 DrawOneSquare(x, y, piece, square_color, 0, tString, bString, 0);
823 GraphExpose(currBoard, x, y, squareSize, squareSize);
824 FlashDelay(flash_delay);
825 DrawOneSquare(x, y, EmptySquare, square_color, 0, tString, bString, 0);
826 GraphExpose(currBoard, x, y, squareSize, squareSize);
827 FlashDelay(flash_delay);
830 DrawOneSquare(x, y, piece, square_color, partnerUp ? 0 : marker[row][column], tString, bString, align);
833 /* Returns 1 if there are "too many" differences between b1 and b2
834 (i.e. more than 1 move was made) */
836 too_many_diffs (Board b1, Board b2)
841 for (i=0; i<BOARD_HEIGHT; ++i) {
842 for (j=0; j<BOARD_WIDTH; ++j) {
843 if (b1[i][j] != b2[i][j]) {
844 if (++c > 4) /* Castling causes 4 diffs */
852 /* Matrix describing castling maneuvers */
853 /* Row, ColRookFrom, ColKingFrom, ColRookTo, ColKingTo */
854 static int castling_matrix[4][5] = {
855 { 0, 0, 4, 3, 2 }, /* 0-0-0, white */
856 { 0, 7, 4, 5, 6 }, /* 0-0, white */
857 { 7, 0, 4, 3, 2 }, /* 0-0-0, black */
858 { 7, 7, 4, 5, 6 } /* 0-0, black */
861 /* Checks whether castling occurred. If it did, *rrow and *rcol
862 are set to the destination (row,col) of the rook that moved.
864 Returns 1 if castling occurred, 0 if not.
866 Note: Only handles a max of 1 castling move, so be sure
867 to call too_many_diffs() first.
870 check_castle_draw (Board newb, Board oldb, int *rrow, int *rcol)
875 /* For each type of castling... */
876 for (i=0; i<4; ++i) {
877 r = castling_matrix[i];
879 /* Check the 4 squares involved in the castling move */
881 for (j=1; j<=4; ++j) {
882 if (newb[r[0]][r[j]] == oldb[r[0]][r[j]]) {
889 /* All 4 changed, so it must be a castling move */
899 SquareExpose(int i, int j, int d)
903 x = lineGap + ((BOARD_WIDTH-1)-j) *
904 (squareSize + lineGap);
905 y = lineGap + i * (squareSize + lineGap);
907 x = lineGap + j * (squareSize + lineGap);
908 y = lineGap + ((BOARD_HEIGHT-1)-i) *
909 (squareSize + lineGap);
911 GraphExpose(currBoard, x-d, y-d, squareSize+2*d, squareSize+2*d);
915 DrawPosition (int repaint, Board board)
917 int i, j, do_flash, exposeAll = False;
918 static int lastFlipView = 0;
919 static int lastBoardValid[2] = {0, 0};
920 static Board lastBoard[2];
921 static char lastMarker[BOARD_RANKS][BOARD_FILES], messedUp;
922 int rrow = -1, rcol = -1;
923 int nr = twoBoards*partnerUp;
927 if(DrawSeekGraph()) return; // [HGM] seekgraph: suppress any drawing if seek graph up
930 if (!lastBoardValid[nr]) return;
931 board = lastBoard[nr];
933 if (!lastBoardValid[nr] || (nr == 0 && lastFlipView != flipView)) {
934 MarkMenuItem("View.Flip View", flipView);
937 if(nr) { SlavePopUp(); SwitchWindow(0); } // [HGM] popup board if not yet popped up, and switch drawing to it.
940 * It would be simpler to clear the window with XClearWindow()
941 * but this causes a very distracting flicker.
944 if (!repaint && lastBoardValid[nr] && (nr == 1 || lastFlipView == flipView)) {
946 /* If too much changes (begin observing new game, etc.), don't
948 do_flash = too_many_diffs(board, lastBoard[nr]) ? 0 : 1;
950 /* Special check for castling so we don't flash both the king
951 and the rook (just flash the king). */
953 /* Mark rook for drawing with NO flashing. */
954 check_castle_draw(board, lastBoard[nr], &rrow, &rcol);
957 /* First pass -- Erase arrow and grid highlights, but keep square content unchanged. Except for new markers. */
958 for (i = 0; i < BOARD_HEIGHT; i++)
959 for (j = 0; j < BOARD_WIDTH; j++)
960 if (damage[nr][i][j] || !nr && marker[i][j] != lastMarker[i][j]) {
961 DrawSquare(i, j, board[i][j], 0);
962 if(lineGap && damage[nr][i][j] & 2) {
963 drawHighlight(j, i, 0);
964 SquareExpose(i, j, 1);
965 } else SquareExpose(i, j, 0);
966 damage[nr][i][j] = 0;
969 /* Second pass -- Draw (newly) empty squares
970 This prevents you from having a piece show up twice while it
971 is flashing on its new square */
972 for (i = 0; i < BOARD_HEIGHT; i++)
973 for (j = 0; j < BOARD_WIDTH; j++)
974 if (board[i][j] != lastBoard[nr][i][j] && board[i][j] == EmptySquare) {
975 DrawSquare(i, j, board[i][j], 0);
976 SquareExpose(i, j, 0);
979 /* Third pass -- Draw piece(s) in new position and flash them */
980 for (i = 0; i < BOARD_HEIGHT; i++)
981 for (j = 0; j < BOARD_WIDTH; j++)
982 if (board[i][j] != lastBoard[nr][i][j]) {
983 DrawSquare(i, j, board[i][j], do_flash && (i != rrow || j != rcol));
984 damage[nr][i][j] = 1; // mark for expose
991 for (i = 0; i < BOARD_HEIGHT; i++)
992 for (j = 0; j < BOARD_WIDTH; j++) {
993 DrawSquare(i, j, board[i][j], 0);
994 damage[nr][i][j] = False;
1000 CopyBoard(lastBoard[nr], board);
1001 lastBoardValid[nr] = 1;
1002 if(nr == 0) { // [HGM] dual: no highlights on second board yet
1003 lastFlipView = flipView;
1004 for (i = 0; i < BOARD_HEIGHT; i++)
1005 for (j = 0; j < BOARD_WIDTH; j++)
1006 lastMarker[i][j] = marker[i][j];
1008 /* Draw highlights */
1009 if (pm1X >= 0 && pm1Y >= 0) {
1010 drawHighlight(pm1X, pm1Y, 2);
1011 if(lineGap) damage[nr][pm1Y][pm1X] |= 2;
1013 if (pm2X >= 0 && pm2Y >= 0) {
1014 drawHighlight(pm2X, pm2Y, 2);
1015 if(lineGap) damage[nr][pm2Y][pm2X] |= 2;
1017 if (hi1X >= 0 && hi1Y >= 0) {
1018 drawHighlight(hi1X, hi1Y, 1);
1019 if(lineGap) damage[nr][hi1Y][hi1X] |= 2;
1021 if (hi2X >= 0 && hi2Y >= 0) {
1022 drawHighlight(hi2X, hi2Y, 1);
1023 if(lineGap) damage[nr][hi2Y][hi2X] |= 2;
1025 DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y);
1027 else DrawArrowHighlight (board[EP_STATUS-3], board[EP_STATUS-4], board[EP_STATUS-1], board[EP_STATUS-2]);
1029 /* If piece being dragged around board, must redraw that too */
1033 GraphExpose(currBoard, 0, 0, BOARD_WIDTH*(squareSize + lineGap) + lineGap, BOARD_HEIGHT*(squareSize + lineGap) + lineGap);
1035 for (i = 0; i < BOARD_HEIGHT; i++)
1036 for (j = 0; j < BOARD_WIDTH; j++)
1037 if(damage[nr][i][j]) {
1038 if(damage[nr][i][j] & 2) // damage by old or new arrow
1039 SquareExpose(i, j, lineGap);
1041 SquareExpose(i, j, 0);
1042 damage[nr][i][j] = 0;
1046 FlashDelay(0); // this flushes drawing queue;
1047 if(nr) SwitchWindow(1);
1051 if(SubtractTimeMarks(&now, &programStartTime) < 1000) {
1052 DrawSeekBackground(2*squareSize, 3*squareSize, 6*squareSize, 5*squareSize);
1053 DrawText("Right-clicking dialog texts", 2*squareSize + 5, 3*squareSize + 5, 2);
1054 DrawText("pops up help on them", 2*squareSize + 5, (int) (3.3*squareSize) + 5, 2);
1055 GraphExpose(currBoard, 2*squareSize, 3*squareSize, 4*squareSize, 2*squareSize);
1057 } else messedUp = FALSE;
1061 /* [AS] Arrow highlighting support */
1063 static double A_WIDTH = 5; /* Width of arrow body */
1065 #define A_HEIGHT_FACTOR 6 /* Length of arrow "point", relative to body width */
1066 #define A_WIDTH_FACTOR 3 /* Width of arrow "point", relative to body width */
1077 return (int) (x + 0.5);
1081 SquareToPos (int rank, int file, int *x, int *y)
1084 *x = lineGap + ((BOARD_WIDTH-1)-file) * (squareSize + lineGap);
1085 *y = lineGap + rank * (squareSize + lineGap);
1087 *x = lineGap + file * (squareSize + lineGap);
1088 *y = lineGap + ((BOARD_HEIGHT-1)-rank) * (squareSize + lineGap);
1092 /* Draw an arrow between two points using current settings */
1094 DrawArrowBetweenPoints (int s_x, int s_y, int d_x, int d_y)
1097 double dx, dy, j, k, x, y;
1100 int h = (d_y > s_y) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1102 arrow[0].x = s_x + A_WIDTH + 0.5;
1105 arrow[1].x = s_x + A_WIDTH + 0.5;
1106 arrow[1].y = d_y - h;
1108 arrow[2].x = arrow[1].x + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1109 arrow[2].y = d_y - h;
1114 arrow[5].x = arrow[1].x - 2*A_WIDTH + 0.5;
1115 arrow[5].y = d_y - h;
1117 arrow[4].x = arrow[5].x - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1118 arrow[4].y = d_y - h;
1120 arrow[6].x = arrow[1].x - 2*A_WIDTH + 0.5;
1123 else if( d_y == s_y ) {
1124 int w = (d_x > s_x) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1127 arrow[0].y = s_y + A_WIDTH + 0.5;
1129 arrow[1].x = d_x - w;
1130 arrow[1].y = s_y + A_WIDTH + 0.5;
1132 arrow[2].x = d_x - w;
1133 arrow[2].y = arrow[1].y + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1138 arrow[5].x = d_x - w;
1139 arrow[5].y = arrow[1].y - 2*A_WIDTH + 0.5;
1141 arrow[4].x = d_x - w;
1142 arrow[4].y = arrow[5].y - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1145 arrow[6].y = arrow[1].y - 2*A_WIDTH + 0.5;
1148 /* [AS] Needed a lot of paper for this! :-) */
1149 dy = (double) (d_y - s_y) / (double) (d_x - s_x);
1150 dx = (double) (s_x - d_x) / (double) (s_y - d_y);
1152 j = sqrt( Sqr(A_WIDTH) / (1.0 + Sqr(dx)) );
1154 k = sqrt( Sqr(A_WIDTH*A_HEIGHT_FACTOR) / (1.0 + Sqr(dy)) );
1159 arrow[0].x = Round(x - j);
1160 arrow[0].y = Round(y + j*dx);
1162 arrow[1].x = Round(arrow[0].x + 2*j); // [HGM] prevent width to be affected by rounding twice
1163 arrow[1].y = Round(arrow[0].y - 2*j*dx);
1166 x = (double) d_x - k;
1167 y = (double) d_y - k*dy;
1170 x = (double) d_x + k;
1171 y = (double) d_y + k*dy;
1174 x = Round(x); y = Round(y); // [HGM] make sure width of shaft is rounded the same way on both ends
1176 arrow[6].x = Round(x - j);
1177 arrow[6].y = Round(y + j*dx);
1179 arrow[2].x = Round(arrow[6].x + 2*j);
1180 arrow[2].y = Round(arrow[6].y - 2*j*dx);
1182 arrow[3].x = Round(arrow[2].x + j*(A_WIDTH_FACTOR-1));
1183 arrow[3].y = Round(arrow[2].y - j*(A_WIDTH_FACTOR-1)*dx);
1188 arrow[5].x = Round(arrow[6].x - j*(A_WIDTH_FACTOR-1));
1189 arrow[5].y = Round(arrow[6].y + j*(A_WIDTH_FACTOR-1)*dx);
1192 DrawPolygon(arrow, 7);
1193 // Polygon( hdc, arrow, 7 );
1197 ArrowDamage (int s_col, int s_row, int d_col, int d_row)
1199 int hor, vert, i, n = partnerUp * twoBoards, delta = abs(d_row - s_row);
1201 if( 2*(d_row - s_row) > abs(d_col - s_col) ) d_row = 4*d_row + 1; else
1202 if( 2*(s_row - d_row) > abs(d_col - s_col) ) d_row = 4*d_row + 3; else d_row = 4*d_row + 2;
1203 if( 2*(d_col - s_col) > delta ) d_col = 4*d_col + 1; else
1204 if( 2*(s_col - d_col) > delta ) d_col = 4*d_col + 3; else d_col = 4*d_col + 2;
1205 s_row = 4*s_row + 2; s_col = 4*s_col + 2;
1207 hor = 64*s_col; vert = 64*s_row;
1208 for(i=0; i<= 64; i++) {
1209 damage[n][vert+30>>8][hor+30>>8] |= 2;
1210 damage[n][vert-30>>8][hor+30>>8] |= 2;
1211 damage[n][vert+30>>8][hor-30>>8] |= 2;
1212 damage[n][vert-30>>8][hor-30>>8] |= 2;
1213 hor += d_col - s_col; vert += d_row - s_row;
1217 /* [AS] Draw an arrow between two squares */
1219 DrawArrowBetweenSquares (int s_col, int s_row, int d_col, int d_row)
1221 int s_x, s_y, d_x, d_y, delta_y;
1223 if( s_col == d_col && s_row == d_row ) {
1227 /* Get source and destination points */
1228 SquareToPos( s_row, s_col, &s_x, &s_y);
1229 SquareToPos( d_row, d_col, &d_x, &d_y);
1230 delta_y = abs(d_y - s_y);
1232 if( d_y > s_y && 2*(d_y - s_y) > abs(d_x - s_x)) {
1233 d_y += squareSize / 2 - squareSize / 4; // [HGM] round towards same centers on all sides!
1235 else if( d_y < s_y && 2*(s_y - d_y) > abs(d_x - s_x)) {
1236 d_y += squareSize / 2 + squareSize / 4;
1239 d_y += squareSize / 2;
1242 if( d_x > s_x && 2*(d_x - s_x) > delta_y) {
1243 d_x += squareSize / 2 - squareSize / 4;
1245 else if( d_x < s_x && 2*(s_x - d_x) > delta_y) {
1246 d_x += squareSize / 2 + squareSize / 4;
1249 d_x += squareSize / 2;
1252 s_x += squareSize / 2;
1253 s_y += squareSize / 2;
1256 A_WIDTH = squareSize / 14.; //[HGM] make float
1258 DrawArrowBetweenPoints( s_x, s_y, d_x, d_y );
1259 ArrowDamage(s_col, s_row, d_col, d_row);
1263 IsDrawArrowEnabled ()
1265 return (appData.highlightMoveWithArrow || twoBoards && partnerUp) && squareSize >= 32;
1269 DrawArrowHighlight (int fromX, int fromY, int toX,int toY)
1271 if( IsDrawArrowEnabled() && fromX >= 0 && fromY >= 0 && toX >= 0 && toY >= 0)
1272 DrawArrowBetweenSquares(fromX, fromY, toX, toY);