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)
154 int arrow = hi2X >= 0 && hi1Y >= 0 && IsDrawArrowEnabled();
156 if (hi1X != fromX || hi1Y != fromY) {
157 if (hi1X >= 0 && hi1Y >= 0) {
158 drawHighlight(hi1X, hi1Y, 0);
160 } // [HGM] first erase both, then draw new!
162 if (hi2X != toX || hi2Y != toY) {
163 if (hi2X >= 0 && hi2Y >= 0) {
164 drawHighlight(hi2X, hi2Y, 0);
168 if(arrow) // there currently is an arrow displayed
169 ArrowDamage(hi1X, hi1Y, hi2X, hi2Y); // mark which squares it damaged
171 if (hi1X != fromX || hi1Y != fromY) {
172 if (fromX >= 0 && fromY >= 0) {
173 drawHighlight(fromX, fromY, 1);
176 if (hi2X != toX || hi2Y != toY) {
177 if (toX >= 0 && toY >= 0) {
178 drawHighlight(toX, toY, 1);
187 if(arrow || toX >= 0 && fromY >= 0 && IsDrawArrowEnabled())
188 DrawPosition(FALSE, NULL); // repair any arrow damage, or draw a new one
194 SetHighlights(-1, -1, -1, -1);
199 SetPremoveHighlights (int fromX, int fromY, int toX, int toY)
201 if (pm1X != fromX || pm1Y != fromY) {
202 if (pm1X >= 0 && pm1Y >= 0) {
203 drawHighlight(pm1X, pm1Y, 0);
205 if (fromX >= 0 && fromY >= 0) {
206 drawHighlight(fromX, fromY, 2);
209 if (pm2X != toX || pm2Y != toY) {
210 if (pm2X >= 0 && pm2Y >= 0) {
211 drawHighlight(pm2X, pm2Y, 0);
213 if (toX >= 0 && toY >= 0) {
214 drawHighlight(toX, toY, 2);
224 ClearPremoveHighlights ()
226 SetPremoveHighlights(-1, -1, -1, -1);
230 * If the user selects on a border boundary, return -1; if off the board,
231 * return -2. Otherwise map the event coordinate to the square.
234 EventToSquare (int x, int limit)
241 if ((x % (squareSize + lineGap)) >= squareSize)
243 x /= (squareSize + lineGap);
249 /* [HR] determine square color depending on chess variant. */
251 SquareColor (int row, int column)
255 if (gameInfo.variant == VariantXiangqi) {
256 if (column >= 3 && column <= 5 && row >= 0 && row <= 2) {
258 } else if (column >= 3 && column <= 5 && row >= 7 && row <= 9) {
260 } else if (row <= 4) {
266 square_color = ((column + row) % 2) == 1;
269 /* [hgm] holdings: next line makes all holdings squares light */
270 if(column < BOARD_LEFT || column >= BOARD_RGHT) square_color = 1;
272 if ( // [HGM] holdings: blank out area between board and holdings
273 column == BOARD_LEFT-1
274 || column == BOARD_RGHT
275 || (column == BOARD_LEFT-2 && row < BOARD_HEIGHT-gameInfo.holdingsSize)
276 || (column == BOARD_RGHT+1 && row >= gameInfo.holdingsSize) )
277 square_color = 2; // black
282 /* Convert board position to corner of screen rect and color */
285 ScreenSquare (int column, int row, Pnt *pt, int *color)
288 pt->x = lineGap + ((BOARD_WIDTH-1)-column) * (squareSize + lineGap);
289 pt->y = lineGap + row * (squareSize + lineGap);
291 pt->x = lineGap + column * (squareSize + lineGap);
292 pt->y = lineGap + ((BOARD_HEIGHT-1)-row) * (squareSize + lineGap);
294 *color = SquareColor(row, column);
297 /* Convert window coords to square */
300 BoardSquare (int x, int y, int *column, int *row)
302 *column = EventToSquare(x, BOARD_WIDTH);
303 if (flipView && *column >= 0)
304 *column = BOARD_WIDTH - 1 - *column;
305 *row = EventToSquare(y, BOARD_HEIGHT);
306 if (!flipView && *row >= 0)
307 *row = BOARD_HEIGHT - 1 - *row;
310 /* Generate a series of frame coords from start->mid->finish.
311 The movement rate doubles until the half way point is
312 reached, then halves back down to the final destination,
313 which gives a nice slow in/out effect. The algorithmn
314 may seem to generate too many intermediates for short
315 moves, but remember that the purpose is to attract the
316 viewers attention to the piece about to be moved and
317 then to where it ends up. Too few frames would be less
321 Tween (Pnt *start, Pnt *mid, Pnt *finish, int factor, Pnt frames[], int *nFrames)
323 int fraction, n, count;
327 /* Slow in, stepping 1/16th, then 1/8th, ... */
329 for (n = 0; n < factor; n++)
331 for (n = 0; n < factor; n++) {
332 frames[count].x = start->x + (mid->x - start->x) / fraction;
333 frames[count].y = start->y + (mid->y - start->y) / fraction;
335 fraction = fraction / 2;
339 frames[count] = *mid;
342 /* Slow out, stepping 1/2, then 1/4, ... */
344 for (n = 0; n < factor; n++) {
345 frames[count].x = finish->x - (finish->x - mid->x) / fraction;
346 frames[count].y = finish->y - (finish->y - mid->y) / fraction;
348 fraction = fraction * 2;
353 /**** Animation code by Hugh Fisher, DCS, ANU.
355 Known problem: if a window overlapping the board is
356 moved away while a piece is being animated underneath,
357 the newly exposed area won't be updated properly.
358 I can live with this.
360 Known problem: if you look carefully at the animation
361 of pieces in mono mode, they are being drawn as solid
362 shapes without interior detail while moving. Fixing
363 this would be a major complication for minimal return.
368 #undef Max /* just in case */
370 #define Max(a, b) ((a) > (b) ? (a) : (b))
371 #define Min(a, b) ((a) < (b) ? (a) : (b))
374 short int x, y, width, height;
384 SetRect (MyRectangle *rect, int x, int y, int width, int height)
389 rect->height = height;
392 /* Test if two frames overlap. If they do, return
393 intersection rect within old and location of
394 that rect within new. */
397 Intersect ( Pnt *old, Pnt *new, int size, MyRectangle *area, Pnt *pt)
399 if (old->x > new->x + size || new->x > old->x + size ||
400 old->y > new->y + size || new->y > old->y + size) {
403 SetRect(area, Max(new->x - old->x, 0), Max(new->y - old->y, 0),
404 size - abs(old->x - new->x), size - abs(old->y - new->y));
405 pt->x = Max(old->x - new->x, 0);
406 pt->y = Max(old->y - new->y, 0);
411 /* For two overlapping frames, return the rect(s)
412 in the old that do not intersect with the new. */
415 CalcUpdateRects (Pnt *old, Pnt *new, int size, MyRectangle update[], int *nUpdates)
419 /* If old = new (shouldn't happen) then nothing to draw */
420 if (old->x == new->x && old->y == new->y) {
424 /* Work out what bits overlap. Since we know the rects
425 are the same size we don't need a full intersect calc. */
427 /* Top or bottom edge? */
428 if (new->y > old->y) {
429 SetRect(&(update[count]), old->x, old->y, size, new->y - old->y);
431 } else if (old->y > new->y) {
432 SetRect(&(update[count]), old->x, old->y + size - (old->y - new->y),
433 size, old->y - new->y);
436 /* Left or right edge - don't overlap any update calculated above. */
437 if (new->x > old->x) {
438 SetRect(&(update[count]), old->x, Max(new->y, old->y),
439 new->x - old->x, size - abs(new->y - old->y));
441 } else if (old->x > new->x) {
442 SetRect(&(update[count]), new->x + size, Max(new->y, old->y),
443 old->x - new->x, size - abs(new->y - old->y));
450 /* Animate the movement of a single piece */
453 BeginAnimation (AnimNr anr, ChessSquare piece, ChessSquare bgPiece, int startColor, Pnt *start)
455 AnimState *anim = &anims[anr];
457 if(appData.upsideDown && flipView) piece += piece < BlackPawn ? BlackPawn : -BlackPawn;
458 /* The old buffer is initialised with the start square (empty) */
459 if(bgPiece == EmptySquare) {
460 DrawBlank(anr, start->x, start->y, startColor);
462 /* Kludge alert: When gating we want the introduced
463 piece to appear on the from square. To generate an
464 image of it, we draw it on the board, copy the image,
465 and draw the original piece again. */
466 if(piece != bgPiece) DrawSquare(anim->startBoardY, anim->startBoardX, bgPiece, 0);
467 CopyRectangle(anr, DISP, 2,
468 start->x, start->y, squareSize, squareSize,
469 0, 0); // [HGM] zh: unstack in stead of grab
470 if(piece != bgPiece) DrawSquare(anim->startBoardY, anim->startBoardX, piece, 0);
472 anim->prevFrame = *start;
474 SetDragPiece(anr, piece);
478 AnimationFrame (AnimNr anr, Pnt *frame, ChessSquare piece)
480 MyRectangle updates[4];
483 AnimState *anim = &anims[anr];
484 int count, i, x, y, w, h;
486 /* Save what we are about to draw into the new buffer */
487 CopyRectangle(anr, DISP, 0,
488 x = frame->x, y = frame->y, w = squareSize, h = squareSize,
491 /* Erase bits of the previous frame */
492 if (Intersect(&anim->prevFrame, frame, squareSize, &overlap, &pt)) {
493 /* Where the new frame overlapped the previous,
494 the contents in newBuf are wrong. */
495 CopyRectangle(anr, 2, 0,
496 overlap.x, overlap.y,
497 overlap.width, overlap.height,
499 /* Repaint the areas in the old that don't overlap new */
500 CalcUpdateRects(&anim->prevFrame, frame, squareSize, updates, &count);
501 for (i = 0; i < count; i++)
502 CopyRectangle(anr, 2, DISP,
503 updates[i].x - anim->prevFrame.x,
504 updates[i].y - anim->prevFrame.y,
505 updates[i].width, updates[i].height,
506 updates[i].x, updates[i].y);
507 /* [HGM] correct expose rectangle to encompass both overlapping squares */
508 if(x > anim->prevFrame.x) w += x - anim->prevFrame.x, x = anim->prevFrame.x;
509 else w += anim->prevFrame.x - x;
510 if(y > anim->prevFrame.y) h += y - anim->prevFrame.y, y = anim->prevFrame.y;
511 else h += anim->prevFrame.y - y;
513 /* Easy when no overlap */
514 CopyRectangle(anr, 2, DISP,
515 0, 0, squareSize, squareSize,
516 anim->prevFrame.x, anim->prevFrame.y);
517 GraphExpose(currBoard, anim->prevFrame.x, anim->prevFrame.y, squareSize, squareSize);
520 /* Save this frame for next time round */
521 CopyRectangle(anr, 0, 2,
522 0, 0, squareSize, squareSize,
524 anim->prevFrame = *frame;
526 /* Draw piece over original screen contents, not current,
527 and copy entire rect. Wipes out overlapping piece images. */
528 InsertPiece(anr, piece);
529 CopyRectangle(anr, 0, DISP,
530 0, 0, squareSize, squareSize,
532 GraphExpose(currBoard, x, y, w, h);
536 EndAnimation (AnimNr anr, Pnt *finish)
538 MyRectangle updates[4];
542 AnimState *anim = &anims[anr];
544 /* The main code will redraw the final square, so we
545 only need to erase the bits that don't overlap. */
546 if (Intersect(&anim->prevFrame, finish, squareSize, &overlap, &pt)) {
547 CalcUpdateRects(&anim->prevFrame, finish, squareSize, updates, &count);
548 for (i = 0; i < count; i++)
549 CopyRectangle(anr, 2, DISP,
550 updates[i].x - anim->prevFrame.x,
551 updates[i].y - anim->prevFrame.y,
552 updates[i].width, updates[i].height,
553 updates[i].x, updates[i].y);
555 CopyRectangle(anr, 2, DISP,
556 0, 0, squareSize, squareSize,
557 anim->prevFrame.x, anim->prevFrame.y);
562 FrameSequence (AnimNr anr, ChessSquare piece, int startColor, Pnt *start, Pnt *finish, Pnt frames[], int nFrames)
566 BeginAnimation(anr, piece, EmptySquare, startColor, start);
567 for (n = 0; n < nFrames; n++) {
568 AnimationFrame(anr, &(frames[n]), piece);
569 FrameDelay(appData.animSpeed);
571 EndAnimation(anr, finish);
575 AnimateAtomicCapture (Board board, int fromX, int fromY, int toX, int toY)
578 ChessSquare piece = board[fromY][toY];
579 board[fromY][toY] = EmptySquare;
580 DrawPosition(FALSE, board);
582 x = lineGap + ((BOARD_WIDTH-1)-toX) * (squareSize + lineGap);
583 y = lineGap + toY * (squareSize + lineGap);
585 x = lineGap + toX * (squareSize + lineGap);
586 y = lineGap + ((BOARD_HEIGHT-1)-toY) * (squareSize + lineGap);
588 for(i=1; i<4*kFactor; i++) {
589 int r = squareSize * 9 * i/(20*kFactor - 5);
590 DrawDot(1, x + squareSize/2 - r, y+squareSize/2 - r, 2*r);
591 FrameDelay(appData.animSpeed);
593 board[fromY][toY] = piece;
597 /* Main control logic for deciding what to animate and how */
600 AnimateMove (Board board, int fromX, int fromY, int toX, int toY)
603 int hop, x = toX, y = toY, x2 = kill2X;
604 Pnt start, finish, mid;
605 Pnt frames[kFactor * 2 + 1];
606 int nFrames, startColor, endColor;
608 if(killX >= 0 && IS_LION(board[fromY][fromX])) Roar();
610 /* Are we animating? */
611 if (!appData.animate || appData.blindfold)
614 if(board[toY][toX] == WhiteRook && board[fromY][fromX] == WhiteKing ||
615 board[toY][toX] == BlackRook && board[fromY][fromX] == BlackKing ||
616 board[toY][toX] == WhiteKing && board[fromY][fromX] == WhiteRook || // [HGM] seirawan
617 board[toY][toX] == BlackKing && board[fromY][fromX] == BlackRook)
618 return; // [HGM] FRC: no animtion of FRC castlings, as to-square is not true to-square
620 if (fromY < 0 || fromX < 0 || toX < 0 || toY < 0) return;
621 piece = board[fromY][fromX];
622 if (piece >= EmptySquare) return;
624 if(x2 >= 0) toX = kill2X, toY = kill2Y; else
625 if(killX >= 0) toX = killX, toY = killY; // [HGM] lion: first to kill square
632 hop = abs(fromX-toX) == 1 && abs(fromY-toY) == 2 || abs(fromX-toX) == 2 && abs(fromY-toY) == 1;
635 ScreenSquare(fromX, fromY, &start, &startColor);
636 ScreenSquare(toX, toY, &finish, &endColor);
639 /* Knight: make straight movement then diagonal */
640 if (abs(toY - fromY) < abs(toX - fromX)) {
641 mid.x = start.x + (finish.x - start.x) / 2;
645 mid.y = start.y + (finish.y - start.y) / 2;
648 mid.x = start.x + (finish.x - start.x) / 2;
649 mid.y = start.y + (finish.y - start.y) / 2;
652 /* Don't use as many frames for very short moves */
653 if (abs(toY - fromY) + abs(toX - fromX) <= 2)
654 Tween(&start, &mid, &finish, kFactor - 1, frames, &nFrames);
656 Tween(&start, &mid, &finish, kFactor, frames, &nFrames);
657 FrameSequence(Game, piece, startColor, &start, &finish, frames, nFrames);
658 if(Explode(board, fromX, fromY, toX, toY)) { // mark as damaged
660 for(i=0; i<BOARD_WIDTH; i++) for(j=0; j<BOARD_HEIGHT; j++)
661 if((i-toX)*(i-toX) + (j-toY)*(j-toY) < 6) damage[0][j][i] |= 1 + ((i-toX ^ j-toY) & 1);
664 /* Be sure end square is redrawn */
665 damage[0][toY][toX] |= True;
667 if(toX == x2 && toY == kill2Y) { fromX = toX; fromY = toY; toX = killX; toY = killY; x2 = -1; goto again; } // second leg
668 if(toX != x || toY != y) { fromX = toX; fromY = toY; toX = x; toY = y; goto again; } // second leg
672 ChangeDragPiece (ChessSquare piece)
674 anims[Player].dragPiece = piece;
675 SetDragPiece(Player, piece);
676 damage[0][fromY][fromX] = True;
680 DragPieceMove (int x, int y)
684 /* Are we animating? */
685 if (!appData.animateDragging || appData.blindfold)
689 if (! anims[Player].dragActive)
691 /* Move piece, maintaining same relative position
692 of mouse within square */
693 corner.x = x - anims[Player].mouseDelta.x;
694 corner.y = y - anims[Player].mouseDelta.y;
695 AnimationFrame(Player, &corner, anims[Player].dragPiece);
697 if (appData.highlightDragging) {
699 BoardSquare(x, y, &boardX, &boardY);
700 SetHighlights(fromX, fromY, boardX, boardY);
706 DragPieceEnd (int x, int y)
708 int boardX, boardY, color;
711 /* Are we animating? */
712 if (!appData.animateDragging || appData.blindfold)
716 if (! anims[Player].dragActive)
718 /* Last frame in sequence is square piece is
719 placed on, which may not match mouse exactly. */
720 BoardSquare(x, y, &boardX, &boardY);
721 ScreenSquare(boardX, boardY, &corner, &color);
722 EndAnimation(Player, &corner);
724 /* Be sure end square is redrawn */
725 damage[0][boardY][boardX] = True;
727 /* This prevents weird things happening with fast successive
728 clicks which on my Sun at least can cause motion events
729 without corresponding press/release. */
730 anims[Player].dragActive = False;
734 DragPieceBegin (int x, int y, Boolean instantly)
736 int boardX, boardY, color;
739 /* Are we animating? */
740 if (!appData.animateDragging || appData.blindfold)
743 /* Figure out which square we start in and the
744 mouse position relative to top left corner. */
745 BoardSquare(x, y, &boardX, &boardY);
746 anims[Player].startBoardX = boardX;
747 anims[Player].startBoardY = boardY;
748 ScreenSquare(boardX, boardY, &corner, &color);
749 anims[Player].startSquare = corner;
750 anims[Player].startColor = color;
751 /* As soon as we start dragging, the piece will jump slightly to
752 be centered over the mouse pointer. */
753 anims[Player].mouseDelta.x = squareSize/2;
754 anims[Player].mouseDelta.y = squareSize/2;
755 /* Initialise animation */
756 anims[Player].dragPiece = PieceForSquare(boardX, boardY);
758 if (anims[Player].dragPiece >= 0 && anims[Player].dragPiece < EmptySquare) {
759 ChessSquare bgPiece = EmptySquare;
760 anims[Player].dragActive = True;
761 if(boardX == BOARD_RGHT+1 && PieceForSquare(boardX-1, boardY) > 1 ||
762 boardX == BOARD_LEFT-2 && PieceForSquare(boardX+1, boardY) > 1)
763 bgPiece = anims[Player].dragPiece;
764 if(gatingPiece != EmptySquare) bgPiece = gatingPiece;
765 BeginAnimation(Player, anims[Player].dragPiece, bgPiece, color, &corner);
766 /* Mark this square as needing to be redrawn. Note that
767 we don't remove the piece though, since logically (ie
768 as seen by opponent) the move hasn't been made yet. */
769 damage[0][boardY][boardX] = True;
771 anims[Player].dragActive = False;
775 /* Handle expose event while piece being dragged */
780 if (!anims[Player].dragActive || appData.blindfold)
783 /* What we're doing: logically, the move hasn't been made yet,
784 so the piece is still in it's original square. But visually
785 it's being dragged around the board. So we erase the square
786 that the piece is on and draw it at the last known drag point. */
787 DrawOneSquare(anims[Player].startSquare.x, anims[Player].startSquare.y,
788 EmptySquare, anims[Player].startColor, 0, NULL, NULL, 0);
789 AnimationFrame(Player, &anims[Player].prevFrame, anims[Player].dragPiece);
790 damage[0][anims[Player].startBoardY][anims[Player].startBoardX] = TRUE;
794 DrawSquare (int row, int column, ChessSquare piece, int do_flash)
796 int square_color, x, y, align=0;
798 char tString[3], bString[2];
801 /* Calculate delay in milliseconds (2-delays per complete flash) */
802 flash_delay = 500 / appData.flashRate;
805 x = lineGap + ((BOARD_WIDTH-1)-column) *
806 (squareSize + lineGap);
807 y = lineGap + row * (squareSize + lineGap);
809 x = lineGap + column * (squareSize + lineGap);
810 y = lineGap + ((BOARD_HEIGHT-1)-row) *
811 (squareSize + lineGap);
814 square_color = SquareColor(row, column);
816 bString[1] = bString[0] = NULLCHAR;
817 if (appData.showCoords && row == (flipView ? BOARD_HEIGHT-1 : 0)
818 && column >= BOARD_LEFT && column < BOARD_RGHT) {
819 bString[0] = 'a' + column - BOARD_LEFT;
820 align = 1; // coord in lower-right corner
822 if (appData.showCoords && column == (flipView ? BOARD_RGHT-1 : BOARD_LEFT)) {
823 snprintf(tString, 3, "%d", ONE - '0' + row);
824 align = 2; // coord in upper-left corner
826 if (column == (flipView ? BOARD_LEFT-1 : BOARD_RGHT) && piece > 1 ) {
827 snprintf(tString, 3, "%d", piece);
828 align = 3; // holdings count in upper-right corner
830 if (column == (flipView ? BOARD_RGHT : BOARD_LEFT-1) && piece > 1) {
831 snprintf(tString, 3, "%d", piece);
832 align = 4; // holdings count in upper-left corner
834 if(piece == DarkSquare) square_color = 2;
835 if(square_color == 2 || appData.blindfold) piece = EmptySquare;
837 if (do_flash && piece != EmptySquare && appData.flashCount > 0) {
838 for (i=0; i<appData.flashCount; ++i) {
839 DrawOneSquare(x, y, piece, square_color, 0, tString, bString, 0);
840 GraphExpose(currBoard, x, y, squareSize, squareSize);
841 FlashDelay(flash_delay);
842 DrawOneSquare(x, y, EmptySquare, square_color, 0, tString, bString, 0);
843 GraphExpose(currBoard, x, y, squareSize, squareSize);
844 FlashDelay(flash_delay);
847 DrawOneSquare(x, y, piece, square_color, partnerUp ? 0 : marker[row][column], tString, bString, align);
850 /* Returns 1 if there are "too many" differences between b1 and b2
851 (i.e. more than 1 move was made) */
853 too_many_diffs (Board b1, Board b2)
858 for (i=0; i<BOARD_HEIGHT; ++i) {
859 for (j=0; j<BOARD_WIDTH; ++j) {
860 if (b1[i][j] != b2[i][j]) {
861 if (++c > 4) /* Castling causes 4 diffs */
869 /* Matrix describing castling maneuvers */
870 /* Row, ColRookFrom, ColKingFrom, ColRookTo, ColKingTo */
871 static int castling_matrix[4][5] = {
872 { 0, 0, 4, 3, 2 }, /* 0-0-0, white */
873 { 0, 7, 4, 5, 6 }, /* 0-0, white */
874 { 7, 0, 4, 3, 2 }, /* 0-0-0, black */
875 { 7, 7, 4, 5, 6 } /* 0-0, black */
878 /* Checks whether castling occurred. If it did, *rrow and *rcol
879 are set to the destination (row,col) of the rook that moved.
881 Returns 1 if castling occurred, 0 if not.
883 Note: Only handles a max of 1 castling move, so be sure
884 to call too_many_diffs() first.
887 check_castle_draw (Board newb, Board oldb, int *rrow, int *rcol)
892 /* For each type of castling... */
893 for (i=0; i<4; ++i) {
894 r = castling_matrix[i];
896 /* Check the 4 squares involved in the castling move */
898 for (j=1; j<=4; ++j) {
899 if (newb[r[0]][r[j]] == oldb[r[0]][r[j]]) {
906 /* All 4 changed, so it must be a castling move */
916 DrawPosition (int repaint, Board board)
918 int i, j, do_flash, exposeAll = False;
919 static int lastFlipView = 0;
920 static int lastBoardValid[2] = {0, 0};
921 static Board lastBoard[2];
922 static char lastMarker[BOARD_RANKS][BOARD_FILES], messedUp;
924 int nr = twoBoards*partnerUp;
928 if(DrawSeekGraph()) return; // [HGM] seekgraph: suppress any drawing if seek graph up
931 if (!lastBoardValid[nr]) return;
932 board = lastBoard[nr];
934 if (!lastBoardValid[nr] || (nr == 0 && lastFlipView != flipView)) {
935 MarkMenuItem("View.Flip View", flipView);
938 if(nr) { SlavePopUp(); SwitchWindow(0); } // [HGM] popup board if not yet popped up, and switch drawing to it.
941 * It would be simpler to clear the window with XClearWindow()
942 * but this causes a very distracting flicker.
945 if (!repaint && lastBoardValid[nr] && (nr == 1 || lastFlipView == flipView)) {
947 // if ( lineGap && IsDrawArrowEnabled())
950 /* If too much changes (begin observing new game, etc.), don't
952 do_flash = too_many_diffs(board, lastBoard[nr]) ? 0 : 1;
954 /* Special check for castling so we don't flash both the king
955 and the rook (just flash the king). */
957 if (check_castle_draw(board, lastBoard[nr], &rrow, &rcol)) {
958 /* Mark rook for drawing with NO flashing. */
959 damage[nr][rrow][rcol] |= 1;
963 /* First pass -- Draw (newly) empty squares and repair damage.
964 This prevents you from having a piece show up twice while it
965 is flashing on its new square */
966 for (i = 0; i < BOARD_HEIGHT; i++)
967 for (j = 0; j < BOARD_WIDTH; j++)
968 if (((board[i][j] != lastBoard[nr][i][j] || !nr && marker[i][j] != lastMarker[i][j]) && board[i][j] == EmptySquare)
969 || damage[nr][i][j]) {
970 DrawSquare(i, j, board[i][j], 0);
971 if(damage[nr][i][j] & 2) {
972 drawHighlight(j, i, 0); // repair arrow damage
973 if(lineGap) damage[nr][i][j] = False; // this flushed the square as well
974 } else damage[nr][i][j] = 1; // mark for expose
977 /* Second pass -- Draw piece(s) in new position and flash them */
978 for (i = 0; i < BOARD_HEIGHT; i++)
979 for (j = 0; j < BOARD_WIDTH; j++)
980 if (board[i][j] != lastBoard[nr][i][j] || !nr && marker[i][j] != lastMarker[i][j]) {
981 DrawSquare(i, j, board[i][j], do_flash);
982 damage[nr][i][j] = 1; // mark for expose
988 for (i = 0; i < BOARD_HEIGHT; i++)
989 for (j = 0; j < BOARD_WIDTH; j++) {
990 DrawSquare(i, j, board[i][j], 0);
991 damage[nr][i][j] = False;
997 CopyBoard(lastBoard[nr], board);
998 lastBoardValid[nr] = 1;
999 if(nr == 0) { // [HGM] dual: no highlights on second board yet
1000 lastFlipView = flipView;
1001 for (i = 0; i < BOARD_HEIGHT; i++)
1002 for (j = 0; j < BOARD_WIDTH; j++)
1003 lastMarker[i][j] = marker[i][j];
1005 /* Draw highlights */
1006 if (pm1X >= 0 && pm1Y >= 0) {
1007 drawHighlight(pm1X, pm1Y, 2);
1008 if(lineGap) damage[nr][pm1Y][pm1X] = False;
1010 if (pm2X >= 0 && pm2Y >= 0) {
1011 drawHighlight(pm2X, pm2Y, 2);
1012 if(lineGap) damage[nr][pm2Y][pm2X] = False;
1014 if (hi1X >= 0 && hi1Y >= 0) {
1015 drawHighlight(hi1X, hi1Y, 1);
1016 if(lineGap) damage[nr][hi1Y][hi1X] = False;
1018 if (hi2X >= 0 && hi2Y >= 0) {
1019 drawHighlight(hi2X, hi2Y, 1);
1020 if(lineGap) damage[nr][hi2Y][hi2X] = False;
1022 DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y);
1024 else DrawArrowHighlight (board[EP_STATUS-3], board[EP_STATUS-4], board[EP_STATUS-1], board[EP_STATUS-2]);
1026 /* If piece being dragged around board, must redraw that too */
1030 GraphExpose(currBoard, 0, 0, BOARD_WIDTH*(squareSize + lineGap) + lineGap, BOARD_HEIGHT*(squareSize + lineGap) + lineGap);
1032 for (i = 0; i < BOARD_HEIGHT; i++)
1033 for (j = 0; j < BOARD_WIDTH; j++)
1034 if(damage[nr][i][j]) {
1037 x = lineGap + ((BOARD_WIDTH-1)-j) *
1038 (squareSize + lineGap);
1039 y = lineGap + i * (squareSize + lineGap);
1041 x = lineGap + j * (squareSize + lineGap);
1042 y = lineGap + ((BOARD_HEIGHT-1)-i) *
1043 (squareSize + lineGap);
1045 if(damage[nr][i][j] & 2) // damage by old or new arrow
1046 GraphExpose(currBoard, x - lineGap, y - lineGap, squareSize + 2*lineGap, squareSize + 2*lineGap);
1048 GraphExpose(currBoard, x, y, squareSize, squareSize);
1049 damage[nr][i][j] &= 2; // remember damage by newly drawn error in '2' bit, to schedule it for erasure next draw
1053 FlashDelay(0); // this flushes drawing queue;
1054 if(nr) SwitchWindow(1);
1058 if(SubtractTimeMarks(&now, &programStartTime) < 1000) {
1059 DrawSeekBackground(2*squareSize, 3*squareSize, 6*squareSize, 5*squareSize);
1060 DrawText("Right-clicking dialog texts", 2*squareSize + 5, 3*squareSize + 5, 2);
1061 DrawText("pops up help on them", 2*squareSize + 5, (int) (3.3*squareSize) + 5, 2);
1062 GraphExpose(currBoard, 2*squareSize, 3*squareSize, 4*squareSize, 2*squareSize);
1064 } else messedUp = FALSE;
1068 /* [AS] Arrow highlighting support */
1070 static double A_WIDTH = 5; /* Width of arrow body */
1072 #define A_HEIGHT_FACTOR 6 /* Length of arrow "point", relative to body width */
1073 #define A_WIDTH_FACTOR 3 /* Width of arrow "point", relative to body width */
1084 return (int) (x + 0.5);
1088 SquareToPos (int rank, int file, int *x, int *y)
1091 *x = lineGap + ((BOARD_WIDTH-1)-file) * (squareSize + lineGap);
1092 *y = lineGap + rank * (squareSize + lineGap);
1094 *x = lineGap + file * (squareSize + lineGap);
1095 *y = lineGap + ((BOARD_HEIGHT-1)-rank) * (squareSize + lineGap);
1099 /* Draw an arrow between two points using current settings */
1101 DrawArrowBetweenPoints (int s_x, int s_y, int d_x, int d_y)
1104 double dx, dy, j, k, x, y;
1107 int h = (d_y > s_y) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1109 arrow[0].x = s_x + A_WIDTH + 0.5;
1112 arrow[1].x = s_x + A_WIDTH + 0.5;
1113 arrow[1].y = d_y - h;
1115 arrow[2].x = arrow[1].x + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1116 arrow[2].y = d_y - h;
1121 arrow[5].x = arrow[1].x - 2*A_WIDTH + 0.5;
1122 arrow[5].y = d_y - h;
1124 arrow[4].x = arrow[5].x - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1125 arrow[4].y = d_y - h;
1127 arrow[6].x = arrow[1].x - 2*A_WIDTH + 0.5;
1130 else if( d_y == s_y ) {
1131 int w = (d_x > s_x) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1134 arrow[0].y = s_y + A_WIDTH + 0.5;
1136 arrow[1].x = d_x - w;
1137 arrow[1].y = s_y + A_WIDTH + 0.5;
1139 arrow[2].x = d_x - w;
1140 arrow[2].y = arrow[1].y + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1145 arrow[5].x = d_x - w;
1146 arrow[5].y = arrow[1].y - 2*A_WIDTH + 0.5;
1148 arrow[4].x = d_x - w;
1149 arrow[4].y = arrow[5].y - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1152 arrow[6].y = arrow[1].y - 2*A_WIDTH + 0.5;
1155 /* [AS] Needed a lot of paper for this! :-) */
1156 dy = (double) (d_y - s_y) / (double) (d_x - s_x);
1157 dx = (double) (s_x - d_x) / (double) (s_y - d_y);
1159 j = sqrt( Sqr(A_WIDTH) / (1.0 + Sqr(dx)) );
1161 k = sqrt( Sqr(A_WIDTH*A_HEIGHT_FACTOR) / (1.0 + Sqr(dy)) );
1166 arrow[0].x = Round(x - j);
1167 arrow[0].y = Round(y + j*dx);
1169 arrow[1].x = Round(arrow[0].x + 2*j); // [HGM] prevent width to be affected by rounding twice
1170 arrow[1].y = Round(arrow[0].y - 2*j*dx);
1173 x = (double) d_x - k;
1174 y = (double) d_y - k*dy;
1177 x = (double) d_x + k;
1178 y = (double) d_y + k*dy;
1181 x = Round(x); y = Round(y); // [HGM] make sure width of shaft is rounded the same way on both ends
1183 arrow[6].x = Round(x - j);
1184 arrow[6].y = Round(y + j*dx);
1186 arrow[2].x = Round(arrow[6].x + 2*j);
1187 arrow[2].y = Round(arrow[6].y - 2*j*dx);
1189 arrow[3].x = Round(arrow[2].x + j*(A_WIDTH_FACTOR-1));
1190 arrow[3].y = Round(arrow[2].y - j*(A_WIDTH_FACTOR-1)*dx);
1195 arrow[5].x = Round(arrow[6].x - j*(A_WIDTH_FACTOR-1));
1196 arrow[5].y = Round(arrow[6].y + j*(A_WIDTH_FACTOR-1)*dx);
1199 DrawPolygon(arrow, 7);
1200 // Polygon( hdc, arrow, 7 );
1204 ArrowDamage (int s_col, int s_row, int d_col, int d_row)
1206 int hor, vert, i, n = partnerUp * twoBoards, delta = abs(d_row - s_row);
1208 if( 2*(d_row - s_row) > abs(d_col - s_col) ) d_row = 4*d_row + 1; else
1209 if( 2*(s_row - d_row) > abs(d_col - s_col) ) d_row = 4*d_row + 3; else d_row = 4*d_row + 2;
1210 if( 2*(d_col - s_col) > delta ) d_col = 4*d_col + 1; else
1211 if( 2*(s_col - d_col) > delta ) d_col = 4*d_col + 3; else d_col = 4*d_col + 2;
1212 s_row = 4*s_row + 2; s_col = 4*s_col + 2;
1214 hor = 64*s_col; vert = 64*s_row;
1215 for(i=0; i<= 64; i++) {
1216 damage[n][vert+30>>8][hor+30>>8] |= 2;
1217 damage[n][vert-30>>8][hor+30>>8] |= 2;
1218 damage[n][vert+30>>8][hor-30>>8] |= 2;
1219 damage[n][vert-30>>8][hor-30>>8] |= 2;
1220 hor += d_col - s_col; vert += d_row - s_row;
1224 /* [AS] Draw an arrow between two squares */
1226 DrawArrowBetweenSquares (int s_col, int s_row, int d_col, int d_row)
1228 int s_x, s_y, d_x, d_y, delta_y;
1230 if( s_col == d_col && s_row == d_row ) {
1234 /* Get source and destination points */
1235 SquareToPos( s_row, s_col, &s_x, &s_y);
1236 SquareToPos( d_row, d_col, &d_x, &d_y);
1237 delta_y = abs(d_y - s_y);
1239 if( d_y > s_y && 2*(d_y - s_y) > abs(d_x - s_x)) {
1240 d_y += squareSize / 2 - squareSize / 4; // [HGM] round towards same centers on all sides!
1242 else if( d_y < s_y && 2*(s_y - d_y) > abs(d_x - s_x)) {
1243 d_y += squareSize / 2 + squareSize / 4;
1246 d_y += squareSize / 2;
1249 if( d_x > s_x && 2*(d_x - s_x) > delta_y) {
1250 d_x += squareSize / 2 - squareSize / 4;
1252 else if( d_x < s_x && 2*(s_x - d_x) > delta_y) {
1253 d_x += squareSize / 2 + squareSize / 4;
1256 d_x += squareSize / 2;
1259 s_x += squareSize / 2;
1260 s_y += squareSize / 2;
1263 A_WIDTH = squareSize / 14.; //[HGM] make float
1265 DrawArrowBetweenPoints( s_x, s_y, d_x, d_y );
1266 ArrowDamage(s_col, s_row, d_col, d_row);
1270 IsDrawArrowEnabled ()
1272 return (appData.highlightMoveWithArrow || twoBoards && partnerUp) && squareSize >= 32;
1276 DrawArrowHighlight (int fromX, int fromY, int toX,int toY)
1278 if( IsDrawArrowEnabled() && fromX >= 0 && fromY >= 0 && toX >= 0 && toY >= 0)
1279 DrawArrowBetweenSquares(fromX, fromY, toX, toY);