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 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>
109 #define usleep(t) _sleep2(((t)+500)/1000)
113 int squareSize, lineGap;
115 int damage[2][BOARD_RANKS][BOARD_FILES];
117 /* There can be two pieces being animated at once: a player
118 can begin dragging a piece before the remote opponent has moved. */
120 AnimState anims[NrOfAnims];
122 static void DrawSquare P((int row, int column, ChessSquare piece, int do_flash));
123 static Boolean IsDrawArrowEnabled P((void));
124 static void DrawArrowHighlight P((int fromX, int fromY, int toX,int toY));
125 static void ArrowDamage P((int s_col, int s_row, int d_col, int d_row));
128 drawHighlight (int file, int rank, int type)
132 if (lineGap == 0) return;
135 x = lineGap/2 + ((BOARD_WIDTH-1)-file) *
136 (squareSize + lineGap);
137 y = lineGap/2 + rank * (squareSize + lineGap);
139 x = lineGap/2 + file * (squareSize + lineGap);
140 y = lineGap/2 + ((BOARD_HEIGHT-1)-rank) *
141 (squareSize + lineGap);
144 DrawBorder(x,y, type, lineGap & 1); // pass whether lineGap is odd
147 int hi1X = -1, hi1Y = -1, hi2X = -1, hi2Y = -1;
148 int pm1X = -1, pm1Y = -1, pm2X = -1, pm2Y = -1;
151 SetHighlights (int fromX, int fromY, int toX, int toY)
153 int arrow = hi2X >= 0 && hi1Y >= 0 && IsDrawArrowEnabled();
155 if (hi1X != fromX || hi1Y != fromY) {
156 if (hi1X >= 0 && hi1Y >= 0) {
157 drawHighlight(hi1X, hi1Y, 0);
159 } // [HGM] first erase both, then draw new!
161 if (hi2X != toX || hi2Y != toY) {
162 if (hi2X >= 0 && hi2Y >= 0) {
163 drawHighlight(hi2X, hi2Y, 0);
167 if(arrow) // there currently is an arrow displayed
168 ArrowDamage(hi1X, hi1Y, hi2X, hi2Y); // mark which squares it damaged
170 if (hi1X != fromX || hi1Y != fromY) {
171 if (fromX >= 0 && fromY >= 0) {
172 drawHighlight(fromX, fromY, 1);
175 if (hi2X != toX || hi2Y != toY) {
176 if (toX >= 0 && toY >= 0) {
177 drawHighlight(toX, toY, 1);
186 if(arrow || toX >= 0 && fromY >= 0 && IsDrawArrowEnabled())
187 DrawPosition(FALSE, NULL); // repair any arrow damage, or draw a new one
193 SetHighlights(-1, -1, -1, -1);
198 SetPremoveHighlights (int fromX, int fromY, int toX, int toY)
200 if (pm1X != fromX || pm1Y != fromY) {
201 if (pm1X >= 0 && pm1Y >= 0) {
202 drawHighlight(pm1X, pm1Y, 0);
204 if (fromX >= 0 && fromY >= 0) {
205 drawHighlight(fromX, fromY, 2);
208 if (pm2X != toX || pm2Y != toY) {
209 if (pm2X >= 0 && pm2Y >= 0) {
210 drawHighlight(pm2X, pm2Y, 0);
212 if (toX >= 0 && toY >= 0) {
213 drawHighlight(toX, toY, 2);
223 ClearPremoveHighlights ()
225 SetPremoveHighlights(-1, -1, -1, -1);
229 * If the user selects on a border boundary, return -1; if off the board,
230 * return -2. Otherwise map the event coordinate to the square.
233 EventToSquare (int x, int limit)
240 if ((x % (squareSize + lineGap)) >= squareSize)
242 x /= (squareSize + lineGap);
248 /* [HR] determine square color depending on chess variant. */
250 SquareColor (int row, int column)
254 if (gameInfo.variant == VariantXiangqi) {
255 if (column >= 3 && column <= 5 && row >= 0 && row <= 2) {
257 } else if (column >= 3 && column <= 5 && row >= 7 && row <= 9) {
259 } else if (row <= 4) {
265 square_color = ((column + row) % 2) == 1;
268 /* [hgm] holdings: next line makes all holdings squares light */
269 if(column < BOARD_LEFT || column >= BOARD_RGHT) square_color = 1;
271 if ( // [HGM] holdings: blank out area between board and holdings
272 column == BOARD_LEFT-1
273 || column == BOARD_RGHT
274 || (column == BOARD_LEFT-2 && row < BOARD_HEIGHT-gameInfo.holdingsSize)
275 || (column == BOARD_RGHT+1 && row >= gameInfo.holdingsSize) )
276 square_color = 2; // black
281 /* Convert board position to corner of screen rect and color */
284 ScreenSquare (int column, int row, Pnt *pt, int *color)
287 pt->x = lineGap + ((BOARD_WIDTH-1)-column) * (squareSize + lineGap);
288 pt->y = lineGap + row * (squareSize + lineGap);
290 pt->x = lineGap + column * (squareSize + lineGap);
291 pt->y = lineGap + ((BOARD_HEIGHT-1)-row) * (squareSize + lineGap);
293 *color = SquareColor(row, column);
296 /* Convert window coords to square */
299 BoardSquare (int x, int y, int *column, int *row)
301 *column = EventToSquare(x, BOARD_WIDTH);
302 if (flipView && *column >= 0)
303 *column = BOARD_WIDTH - 1 - *column;
304 *row = EventToSquare(y, BOARD_HEIGHT);
305 if (!flipView && *row >= 0)
306 *row = BOARD_HEIGHT - 1 - *row;
309 /* Generate a series of frame coords from start->mid->finish.
310 The movement rate doubles until the half way point is
311 reached, then halves back down to the final destination,
312 which gives a nice slow in/out effect. The algorithmn
313 may seem to generate too many intermediates for short
314 moves, but remember that the purpose is to attract the
315 viewers attention to the piece about to be moved and
316 then to where it ends up. Too few frames would be less
320 Tween (Pnt *start, Pnt *mid, Pnt *finish, int factor, Pnt frames[], int *nFrames)
322 int fraction, n, count;
326 /* Slow in, stepping 1/16th, then 1/8th, ... */
328 for (n = 0; n < factor; n++)
330 for (n = 0; n < factor; n++) {
331 frames[count].x = start->x + (mid->x - start->x) / fraction;
332 frames[count].y = start->y + (mid->y - start->y) / fraction;
334 fraction = fraction / 2;
338 frames[count] = *mid;
341 /* Slow out, stepping 1/2, then 1/4, ... */
343 for (n = 0; n < factor; n++) {
344 frames[count].x = finish->x - (finish->x - mid->x) / fraction;
345 frames[count].y = finish->y - (finish->y - mid->y) / fraction;
347 fraction = fraction * 2;
352 /**** Animation code by Hugh Fisher, DCS, ANU.
354 Known problem: if a window overlapping the board is
355 moved away while a piece is being animated underneath,
356 the newly exposed area won't be updated properly.
357 I can live with this.
359 Known problem: if you look carefully at the animation
360 of pieces in mono mode, they are being drawn as solid
361 shapes without interior detail while moving. Fixing
362 this would be a major complication for minimal return.
367 #undef Max /* just in case */
369 #define Max(a, b) ((a) > (b) ? (a) : (b))
370 #define Min(a, b) ((a) < (b) ? (a) : (b))
373 short int x, y, width, height;
383 SetRect (MyRectangle *rect, int x, int y, int width, int height)
388 rect->height = height;
391 /* Test if two frames overlap. If they do, return
392 intersection rect within old and location of
393 that rect within new. */
396 Intersect ( Pnt *old, Pnt *new, int size, MyRectangle *area, Pnt *pt)
398 if (old->x > new->x + size || new->x > old->x + size ||
399 old->y > new->y + size || new->y > old->y + size) {
402 SetRect(area, Max(new->x - old->x, 0), Max(new->y - old->y, 0),
403 size - abs(old->x - new->x), size - abs(old->y - new->y));
404 pt->x = Max(old->x - new->x, 0);
405 pt->y = Max(old->y - new->y, 0);
410 /* For two overlapping frames, return the rect(s)
411 in the old that do not intersect with the new. */
414 CalcUpdateRects (Pnt *old, Pnt *new, int size, MyRectangle update[], int *nUpdates)
418 /* If old = new (shouldn't happen) then nothing to draw */
419 if (old->x == new->x && old->y == new->y) {
423 /* Work out what bits overlap. Since we know the rects
424 are the same size we don't need a full intersect calc. */
426 /* Top or bottom edge? */
427 if (new->y > old->y) {
428 SetRect(&(update[count]), old->x, old->y, size, new->y - old->y);
430 } else if (old->y > new->y) {
431 SetRect(&(update[count]), old->x, old->y + size - (old->y - new->y),
432 size, old->y - new->y);
435 /* Left or right edge - don't overlap any update calculated above. */
436 if (new->x > old->x) {
437 SetRect(&(update[count]), old->x, Max(new->y, old->y),
438 new->x - old->x, size - abs(new->y - old->y));
440 } else if (old->x > new->x) {
441 SetRect(&(update[count]), new->x + size, Max(new->y, old->y),
442 old->x - new->x, size - abs(new->y - old->y));
449 /* Animate the movement of a single piece */
452 BeginAnimation (AnimNr anr, ChessSquare piece, ChessSquare bgPiece, int startColor, Pnt *start)
454 AnimState *anim = &anims[anr];
456 if(appData.upsideDown && flipView) piece += piece < BlackPawn ? BlackPawn : -BlackPawn;
457 /* The old buffer is initialised with the start square (empty) */
458 if(bgPiece == EmptySquare) {
459 DrawBlank(anr, start->x, start->y, startColor);
461 /* Kludge alert: When gating we want the introduced
462 piece to appear on the from square. To generate an
463 image of it, we draw it on the board, copy the image,
464 and draw the original piece again. */
465 if(piece != bgPiece) DrawSquare(anim->startBoardY, anim->startBoardX, bgPiece, 0);
466 CopyRectangle(anr, DISP, 2,
467 start->x, start->y, squareSize, squareSize,
468 0, 0); // [HGM] zh: unstack in stead of grab
469 if(piece != bgPiece) DrawSquare(anim->startBoardY, anim->startBoardX, piece, 0);
471 anim->prevFrame = *start;
473 SetDragPiece(anr, piece);
477 AnimationFrame (AnimNr anr, Pnt *frame, ChessSquare piece)
479 MyRectangle updates[4];
482 AnimState *anim = &anims[anr];
483 int count, i, x, y, w, h;
485 /* Save what we are about to draw into the new buffer */
486 CopyRectangle(anr, DISP, 0,
487 x = frame->x, y = frame->y, w = squareSize, h = squareSize,
490 /* Erase bits of the previous frame */
491 if (Intersect(&anim->prevFrame, frame, squareSize, &overlap, &pt)) {
492 /* Where the new frame overlapped the previous,
493 the contents in newBuf are wrong. */
494 CopyRectangle(anr, 2, 0,
495 overlap.x, overlap.y,
496 overlap.width, overlap.height,
498 /* Repaint the areas in the old that don't overlap new */
499 CalcUpdateRects(&anim->prevFrame, frame, squareSize, updates, &count);
500 for (i = 0; i < count; i++)
501 CopyRectangle(anr, 2, DISP,
502 updates[i].x - anim->prevFrame.x,
503 updates[i].y - anim->prevFrame.y,
504 updates[i].width, updates[i].height,
505 updates[i].x, updates[i].y);
506 /* [HGM] correct expose rectangle to encompass both overlapping squares */
507 if(x > anim->prevFrame.x) w += x - anim->prevFrame.x, x = anim->prevFrame.x;
508 else w += anim->prevFrame.x - x;
509 if(y > anim->prevFrame.y) h += y - anim->prevFrame.y, y = anim->prevFrame.y;
510 else h += anim->prevFrame.y - y;
512 /* Easy when no overlap */
513 CopyRectangle(anr, 2, DISP,
514 0, 0, squareSize, squareSize,
515 anim->prevFrame.x, anim->prevFrame.y);
516 GraphExpose(currBoard, anim->prevFrame.x, anim->prevFrame.y, squareSize, squareSize);
519 /* Save this frame for next time round */
520 CopyRectangle(anr, 0, 2,
521 0, 0, squareSize, squareSize,
523 anim->prevFrame = *frame;
525 /* Draw piece over original screen contents, not current,
526 and copy entire rect. Wipes out overlapping piece images. */
527 InsertPiece(anr, piece);
528 CopyRectangle(anr, 0, DISP,
529 0, 0, squareSize, squareSize,
531 GraphExpose(currBoard, x, y, w, h);
535 EndAnimation (AnimNr anr, Pnt *finish)
537 MyRectangle updates[4];
541 AnimState *anim = &anims[anr];
543 /* The main code will redraw the final square, so we
544 only need to erase the bits that don't overlap. */
545 if (Intersect(&anim->prevFrame, finish, squareSize, &overlap, &pt)) {
546 CalcUpdateRects(&anim->prevFrame, finish, squareSize, updates, &count);
547 for (i = 0; i < count; i++)
548 CopyRectangle(anr, 2, DISP,
549 updates[i].x - anim->prevFrame.x,
550 updates[i].y - anim->prevFrame.y,
551 updates[i].width, updates[i].height,
552 updates[i].x, updates[i].y);
554 CopyRectangle(anr, 2, DISP,
555 0, 0, squareSize, squareSize,
556 anim->prevFrame.x, anim->prevFrame.y);
561 FrameSequence (AnimNr anr, ChessSquare piece, int startColor, Pnt *start, Pnt *finish, Pnt frames[], int nFrames)
565 BeginAnimation(anr, piece, EmptySquare, startColor, start);
566 for (n = 0; n < nFrames; n++) {
567 AnimationFrame(anr, &(frames[n]), piece);
568 FrameDelay(appData.animSpeed);
570 EndAnimation(anr, finish);
574 AnimateAtomicCapture (Board board, int fromX, int fromY, int toX, int toY)
577 ChessSquare piece = board[fromY][toY];
578 board[fromY][toY] = EmptySquare;
579 DrawPosition(FALSE, board);
581 x = lineGap + ((BOARD_WIDTH-1)-toX) * (squareSize + lineGap);
582 y = lineGap + toY * (squareSize + lineGap);
584 x = lineGap + toX * (squareSize + lineGap);
585 y = lineGap + ((BOARD_HEIGHT-1)-toY) * (squareSize + lineGap);
587 for(i=1; i<4*kFactor; i++) {
588 int r = squareSize * 9 * i/(20*kFactor - 5);
589 DrawDot(1, x + squareSize/2 - r, y+squareSize/2 - r, 2*r);
590 FrameDelay(appData.animSpeed);
592 board[fromY][toY] = piece;
596 /* Main control logic for deciding what to animate and how */
599 AnimateMove (Board board, int fromX, int fromY, int toX, int toY)
602 int hop, x = toX, y = toY;
603 Pnt start, finish, mid;
604 Pnt frames[kFactor * 2 + 1];
605 int nFrames, startColor, endColor;
607 if(killX >= 0 && IS_LION(board[fromY][fromX])) Roar();
609 /* Are we animating? */
610 if (!appData.animate || appData.blindfold)
613 if(board[toY][toX] == WhiteRook && board[fromY][fromX] == WhiteKing ||
614 board[toY][toX] == BlackRook && board[fromY][fromX] == BlackKing ||
615 board[toY][toX] == WhiteKing && board[fromY][fromX] == WhiteRook || // [HGM] seirawan
616 board[toY][toX] == BlackKing && board[fromY][fromX] == BlackRook)
617 return; // [HGM] FRC: no animtion of FRC castlings, as to-square is not true to-square
619 if (fromY < 0 || fromX < 0 || toX < 0 || toY < 0) return;
620 piece = board[fromY][fromX];
621 if (piece >= EmptySquare) return;
623 if(killX >= 0) toX = killX, toY = killY; // [HGM] lion: first to kill square
630 hop = abs(fromX-toX) == 1 && abs(fromY-toY) == 2 || abs(fromX-toX) == 2 && abs(fromY-toY) == 1;
633 ScreenSquare(fromX, fromY, &start, &startColor);
634 ScreenSquare(toX, toY, &finish, &endColor);
637 /* Knight: make straight movement then diagonal */
638 if (abs(toY - fromY) < abs(toX - fromX)) {
639 mid.x = start.x + (finish.x - start.x) / 2;
643 mid.y = start.y + (finish.y - start.y) / 2;
646 mid.x = start.x + (finish.x - start.x) / 2;
647 mid.y = start.y + (finish.y - start.y) / 2;
650 /* Don't use as many frames for very short moves */
651 if (abs(toY - fromY) + abs(toX - fromX) <= 2)
652 Tween(&start, &mid, &finish, kFactor - 1, frames, &nFrames);
654 Tween(&start, &mid, &finish, kFactor, frames, &nFrames);
655 FrameSequence(Game, piece, startColor, &start, &finish, frames, nFrames);
656 if(Explode(board, fromX, fromY, toX, toY)) { // mark as damaged
658 for(i=0; i<BOARD_WIDTH; i++) for(j=0; j<BOARD_HEIGHT; j++)
659 if((i-toX)*(i-toX) + (j-toY)*(j-toY) < 6) damage[0][j][i] |= 1 + ((i-toX ^ j-toY) & 1);
662 /* Be sure end square is redrawn */
663 damage[0][toY][toX] |= True;
665 if(toX != x || toY != y) { fromX = toX; fromY = toY; toX = x; toY = y; goto again; } // second leg
669 ChangeDragPiece (ChessSquare piece)
671 anims[Player].dragPiece = piece;
672 SetDragPiece(Player, piece);
676 DragPieceMove (int x, int y)
680 /* Are we animating? */
681 if (!appData.animateDragging || appData.blindfold)
685 if (! anims[Player].dragActive)
687 /* Move piece, maintaining same relative position
688 of mouse within square */
689 corner.x = x - anims[Player].mouseDelta.x;
690 corner.y = y - anims[Player].mouseDelta.y;
691 AnimationFrame(Player, &corner, anims[Player].dragPiece);
693 if (appData.highlightDragging) {
695 BoardSquare(x, y, &boardX, &boardY);
696 SetHighlights(fromX, fromY, boardX, boardY);
702 DragPieceEnd (int x, int y)
704 int boardX, boardY, color;
707 /* Are we animating? */
708 if (!appData.animateDragging || appData.blindfold)
712 if (! anims[Player].dragActive)
714 /* Last frame in sequence is square piece is
715 placed on, which may not match mouse exactly. */
716 BoardSquare(x, y, &boardX, &boardY);
717 ScreenSquare(boardX, boardY, &corner, &color);
718 EndAnimation(Player, &corner);
720 /* Be sure end square is redrawn */
721 damage[0][boardY][boardX] = True;
723 /* This prevents weird things happening with fast successive
724 clicks which on my Sun at least can cause motion events
725 without corresponding press/release. */
726 anims[Player].dragActive = False;
730 DragPieceBegin (int x, int y, Boolean instantly)
732 int boardX, boardY, color;
735 /* Are we animating? */
736 if (!appData.animateDragging || appData.blindfold)
739 /* Figure out which square we start in and the
740 mouse position relative to top left corner. */
741 BoardSquare(x, y, &boardX, &boardY);
742 anims[Player].startBoardX = boardX;
743 anims[Player].startBoardY = boardY;
744 ScreenSquare(boardX, boardY, &corner, &color);
745 anims[Player].startSquare = corner;
746 anims[Player].startColor = color;
747 /* As soon as we start dragging, the piece will jump slightly to
748 be centered over the mouse pointer. */
749 anims[Player].mouseDelta.x = squareSize/2;
750 anims[Player].mouseDelta.y = squareSize/2;
751 /* Initialise animation */
752 anims[Player].dragPiece = PieceForSquare(boardX, boardY);
754 if (anims[Player].dragPiece >= 0 && anims[Player].dragPiece < EmptySquare) {
755 ChessSquare bgPiece = EmptySquare;
756 anims[Player].dragActive = True;
757 if(boardX == BOARD_RGHT+1 && PieceForSquare(boardX-1, boardY) > 1 ||
758 boardX == BOARD_LEFT-2 && PieceForSquare(boardX+1, boardY) > 1)
759 bgPiece = anims[Player].dragPiece;
760 if(gatingPiece != EmptySquare) bgPiece = gatingPiece;
761 BeginAnimation(Player, anims[Player].dragPiece, bgPiece, color, &corner);
762 /* Mark this square as needing to be redrawn. Note that
763 we don't remove the piece though, since logically (ie
764 as seen by opponent) the move hasn't been made yet. */
765 damage[0][boardY][boardX] = True;
767 anims[Player].dragActive = False;
771 /* Handle expose event while piece being dragged */
776 if (!anims[Player].dragActive || appData.blindfold)
779 /* What we're doing: logically, the move hasn't been made yet,
780 so the piece is still in it's original square. But visually
781 it's being dragged around the board. So we erase the square
782 that the piece is on and draw it at the last known drag point. */
783 DrawOneSquare(anims[Player].startSquare.x, anims[Player].startSquare.y,
784 EmptySquare, anims[Player].startColor, 0, NULL, NULL, 0);
785 AnimationFrame(Player, &anims[Player].prevFrame, anims[Player].dragPiece);
786 damage[0][anims[Player].startBoardY][anims[Player].startBoardX] = TRUE;
790 DrawSquare (int row, int column, ChessSquare piece, int do_flash)
792 int square_color, x, y, align=0;
794 char tString[3], bString[2];
797 /* Calculate delay in milliseconds (2-delays per complete flash) */
798 flash_delay = 500 / appData.flashRate;
801 x = lineGap + ((BOARD_WIDTH-1)-column) *
802 (squareSize + lineGap);
803 y = lineGap + row * (squareSize + lineGap);
805 x = lineGap + column * (squareSize + lineGap);
806 y = lineGap + ((BOARD_HEIGHT-1)-row) *
807 (squareSize + lineGap);
810 square_color = SquareColor(row, column);
812 bString[1] = bString[0] = NULLCHAR;
813 if (appData.showCoords && row == (flipView ? BOARD_HEIGHT-1 : 0)
814 && column >= BOARD_LEFT && column < BOARD_RGHT) {
815 bString[0] = 'a' + column - BOARD_LEFT;
816 align = 1; // coord in lower-right corner
818 if (appData.showCoords && column == (flipView ? BOARD_RGHT-1 : BOARD_LEFT)) {
819 snprintf(tString, 3, "%d", ONE - '0' + row);
820 align = 2; // coord in upper-left corner
822 if (column == (flipView ? BOARD_LEFT-1 : BOARD_RGHT) && piece > 1 ) {
823 snprintf(tString, 3, "%d", piece);
824 align = 3; // holdings count in upper-right corner
826 if (column == (flipView ? BOARD_RGHT : BOARD_LEFT-1) && piece > 1) {
827 snprintf(tString, 3, "%d", piece);
828 align = 4; // holdings count in upper-left corner
830 if(piece == DarkSquare) square_color = 2;
831 if(square_color == 2 || appData.blindfold) piece = EmptySquare;
833 if (do_flash && piece != EmptySquare && appData.flashCount > 0) {
834 for (i=0; i<appData.flashCount; ++i) {
835 DrawOneSquare(x, y, piece, square_color, 0, tString, bString, 0);
836 GraphExpose(currBoard, x, y, squareSize, squareSize);
837 FlashDelay(flash_delay);
838 DrawOneSquare(x, y, EmptySquare, square_color, 0, tString, bString, 0);
839 GraphExpose(currBoard, x, y, squareSize, squareSize);
840 FlashDelay(flash_delay);
843 DrawOneSquare(x, y, piece, square_color, partnerUp ? 0 : marker[row][column], tString, bString, align);
846 /* Returns 1 if there are "too many" differences between b1 and b2
847 (i.e. more than 1 move was made) */
849 too_many_diffs (Board b1, Board b2)
854 for (i=0; i<BOARD_HEIGHT; ++i) {
855 for (j=0; j<BOARD_WIDTH; ++j) {
856 if (b1[i][j] != b2[i][j]) {
857 if (++c > 4) /* Castling causes 4 diffs */
865 /* Matrix describing castling maneuvers */
866 /* Row, ColRookFrom, ColKingFrom, ColRookTo, ColKingTo */
867 static int castling_matrix[4][5] = {
868 { 0, 0, 4, 3, 2 }, /* 0-0-0, white */
869 { 0, 7, 4, 5, 6 }, /* 0-0, white */
870 { 7, 0, 4, 3, 2 }, /* 0-0-0, black */
871 { 7, 7, 4, 5, 6 } /* 0-0, black */
874 /* Checks whether castling occurred. If it did, *rrow and *rcol
875 are set to the destination (row,col) of the rook that moved.
877 Returns 1 if castling occurred, 0 if not.
879 Note: Only handles a max of 1 castling move, so be sure
880 to call too_many_diffs() first.
883 check_castle_draw (Board newb, Board oldb, int *rrow, int *rcol)
888 /* For each type of castling... */
889 for (i=0; i<4; ++i) {
890 r = castling_matrix[i];
892 /* Check the 4 squares involved in the castling move */
894 for (j=1; j<=4; ++j) {
895 if (newb[r[0]][r[j]] == oldb[r[0]][r[j]]) {
902 /* All 4 changed, so it must be a castling move */
912 DrawPosition (int repaint, Board board)
914 int i, j, do_flash, exposeAll = False;
915 static int lastFlipView = 0;
916 static int lastBoardValid[2] = {0, 0};
917 static Board lastBoard[2];
918 static char lastMarker[BOARD_RANKS][BOARD_FILES];
920 int nr = twoBoards*partnerUp;
922 if(DrawSeekGraph()) return; // [HGM] seekgraph: suppress any drawing if seek graph up
925 if (!lastBoardValid[nr]) return;
926 board = lastBoard[nr];
928 if (!lastBoardValid[nr] || (nr == 0 && lastFlipView != flipView)) {
929 MarkMenuItem("View.Flip View", flipView);
932 if(nr) { SlavePopUp(); SwitchWindow(0); } // [HGM] popup board if not yet popped up, and switch drawing to it.
935 * It would be simpler to clear the window with XClearWindow()
936 * but this causes a very distracting flicker.
939 if (!repaint && lastBoardValid[nr] && (nr == 1 || lastFlipView == flipView)) {
941 // if ( lineGap && IsDrawArrowEnabled())
944 /* If too much changes (begin observing new game, etc.), don't
946 do_flash = too_many_diffs(board, lastBoard[nr]) ? 0 : 1;
948 /* Special check for castling so we don't flash both the king
949 and the rook (just flash the king). */
951 if (check_castle_draw(board, lastBoard[nr], &rrow, &rcol)) {
952 /* Mark rook for drawing with NO flashing. */
953 damage[nr][rrow][rcol] |= 1;
957 /* First pass -- Draw (newly) empty squares and repair damage.
958 This prevents you from having a piece show up twice while it
959 is flashing on its new square */
960 for (i = 0; i < BOARD_HEIGHT; i++)
961 for (j = 0; j < BOARD_WIDTH; j++)
962 if (((board[i][j] != lastBoard[nr][i][j] || !nr && marker[i][j] != lastMarker[i][j]) && board[i][j] == EmptySquare)
963 || damage[nr][i][j]) {
964 DrawSquare(i, j, board[i][j], 0);
965 if(damage[nr][i][j] & 2) {
966 drawHighlight(j, i, 0); // repair arrow damage
967 if(lineGap) damage[nr][i][j] = False; // this flushed the square as well
968 } else damage[nr][i][j] = 1; // mark for expose
971 /* Second pass -- Draw piece(s) in new position and flash them */
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] || !nr && marker[i][j] != lastMarker[i][j]) {
975 DrawSquare(i, j, board[i][j], do_flash);
976 damage[nr][i][j] = 1; // mark for expose
982 for (i = 0; i < BOARD_HEIGHT; i++)
983 for (j = 0; j < BOARD_WIDTH; j++) {
984 DrawSquare(i, j, board[i][j], 0);
985 damage[nr][i][j] = False;
991 CopyBoard(lastBoard[nr], board);
992 lastBoardValid[nr] = 1;
993 if(nr == 0) { // [HGM] dual: no highlights on second board yet
994 lastFlipView = flipView;
995 for (i = 0; i < BOARD_HEIGHT; i++)
996 for (j = 0; j < BOARD_WIDTH; j++)
997 lastMarker[i][j] = marker[i][j];
999 /* Draw highlights */
1000 if (pm1X >= 0 && pm1Y >= 0) {
1001 drawHighlight(pm1X, pm1Y, 2);
1002 if(lineGap) damage[nr][pm1Y][pm1X] = False;
1004 if (pm2X >= 0 && pm2Y >= 0) {
1005 drawHighlight(pm2X, pm2Y, 2);
1006 if(lineGap) damage[nr][pm2Y][pm2X] = False;
1008 if (hi1X >= 0 && hi1Y >= 0) {
1009 drawHighlight(hi1X, hi1Y, 1);
1010 if(lineGap) damage[nr][hi1Y][hi1X] = False;
1012 if (hi2X >= 0 && hi2Y >= 0) {
1013 drawHighlight(hi2X, hi2Y, 1);
1014 if(lineGap) damage[nr][hi2Y][hi2X] = False;
1016 DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y);
1018 else DrawArrowHighlight (board[EP_STATUS-3], board[EP_STATUS-4], board[EP_STATUS-1], board[EP_STATUS-2]);
1020 /* If piece being dragged around board, must redraw that too */
1024 GraphExpose(currBoard, 0, 0, BOARD_WIDTH*(squareSize + lineGap) + lineGap, BOARD_HEIGHT*(squareSize + lineGap) + lineGap);
1026 for (i = 0; i < BOARD_HEIGHT; i++)
1027 for (j = 0; j < BOARD_WIDTH; j++)
1028 if(damage[nr][i][j]) {
1031 x = lineGap + ((BOARD_WIDTH-1)-j) *
1032 (squareSize + lineGap);
1033 y = lineGap + i * (squareSize + lineGap);
1035 x = lineGap + j * (squareSize + lineGap);
1036 y = lineGap + ((BOARD_HEIGHT-1)-i) *
1037 (squareSize + lineGap);
1039 if(damage[nr][i][j] & 2) // damage by old or new arrow
1040 GraphExpose(currBoard, x - lineGap, y - lineGap, squareSize + 2*lineGap, squareSize + 2*lineGap);
1042 GraphExpose(currBoard, x, y, squareSize, squareSize);
1043 damage[nr][i][j] &= 2; // remember damage by newly drawn error in '2' bit, to schedule it for erasure next draw
1047 FlashDelay(0); // this flushes drawing queue;
1048 if(nr) SwitchWindow(1);
1051 /* [AS] Arrow highlighting support */
1053 static double A_WIDTH = 5; /* Width of arrow body */
1055 #define A_HEIGHT_FACTOR 6 /* Length of arrow "point", relative to body width */
1056 #define A_WIDTH_FACTOR 3 /* Width of arrow "point", relative to body width */
1067 return (int) (x + 0.5);
1071 SquareToPos (int rank, int file, int *x, int *y)
1074 *x = lineGap + ((BOARD_WIDTH-1)-file) * (squareSize + lineGap);
1075 *y = lineGap + rank * (squareSize + lineGap);
1077 *x = lineGap + file * (squareSize + lineGap);
1078 *y = lineGap + ((BOARD_HEIGHT-1)-rank) * (squareSize + lineGap);
1082 /* Draw an arrow between two points using current settings */
1084 DrawArrowBetweenPoints (int s_x, int s_y, int d_x, int d_y)
1087 double dx, dy, j, k, x, y;
1090 int h = (d_y > s_y) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1092 arrow[0].x = s_x + A_WIDTH + 0.5;
1095 arrow[1].x = s_x + A_WIDTH + 0.5;
1096 arrow[1].y = d_y - h;
1098 arrow[2].x = arrow[1].x + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1099 arrow[2].y = d_y - h;
1104 arrow[5].x = arrow[1].x - 2*A_WIDTH + 0.5;
1105 arrow[5].y = d_y - h;
1107 arrow[4].x = arrow[5].x - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1108 arrow[4].y = d_y - h;
1110 arrow[6].x = arrow[1].x - 2*A_WIDTH + 0.5;
1113 else if( d_y == s_y ) {
1114 int w = (d_x > s_x) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
1117 arrow[0].y = s_y + A_WIDTH + 0.5;
1119 arrow[1].x = d_x - w;
1120 arrow[1].y = s_y + A_WIDTH + 0.5;
1122 arrow[2].x = d_x - w;
1123 arrow[2].y = arrow[1].y + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1128 arrow[5].x = d_x - w;
1129 arrow[5].y = arrow[1].y - 2*A_WIDTH + 0.5;
1131 arrow[4].x = d_x - w;
1132 arrow[4].y = arrow[5].y - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
1135 arrow[6].y = arrow[1].y - 2*A_WIDTH + 0.5;
1138 /* [AS] Needed a lot of paper for this! :-) */
1139 dy = (double) (d_y - s_y) / (double) (d_x - s_x);
1140 dx = (double) (s_x - d_x) / (double) (s_y - d_y);
1142 j = sqrt( Sqr(A_WIDTH) / (1.0 + Sqr(dx)) );
1144 k = sqrt( Sqr(A_WIDTH*A_HEIGHT_FACTOR) / (1.0 + Sqr(dy)) );
1149 arrow[0].x = Round(x - j);
1150 arrow[0].y = Round(y + j*dx);
1152 arrow[1].x = Round(arrow[0].x + 2*j); // [HGM] prevent width to be affected by rounding twice
1153 arrow[1].y = Round(arrow[0].y - 2*j*dx);
1156 x = (double) d_x - k;
1157 y = (double) d_y - k*dy;
1160 x = (double) d_x + k;
1161 y = (double) d_y + k*dy;
1164 x = Round(x); y = Round(y); // [HGM] make sure width of shaft is rounded the same way on both ends
1166 arrow[6].x = Round(x - j);
1167 arrow[6].y = Round(y + j*dx);
1169 arrow[2].x = Round(arrow[6].x + 2*j);
1170 arrow[2].y = Round(arrow[6].y - 2*j*dx);
1172 arrow[3].x = Round(arrow[2].x + j*(A_WIDTH_FACTOR-1));
1173 arrow[3].y = Round(arrow[2].y - j*(A_WIDTH_FACTOR-1)*dx);
1178 arrow[5].x = Round(arrow[6].x - j*(A_WIDTH_FACTOR-1));
1179 arrow[5].y = Round(arrow[6].y + j*(A_WIDTH_FACTOR-1)*dx);
1182 DrawPolygon(arrow, 7);
1183 // Polygon( hdc, arrow, 7 );
1187 ArrowDamage (int s_col, int s_row, int d_col, int d_row)
1189 int hor, vert, i, n = partnerUp * twoBoards;
1190 hor = 64*s_col + 32; vert = 64*s_row + 32;
1191 for(i=0; i<= 64; i++) {
1192 damage[n][vert+6>>6][hor+6>>6] |= 2;
1193 damage[n][vert-6>>6][hor+6>>6] |= 2;
1194 damage[n][vert+6>>6][hor-6>>6] |= 2;
1195 damage[n][vert-6>>6][hor-6>>6] |= 2;
1196 hor += d_col - s_col; vert += d_row - s_row;
1200 /* [AS] Draw an arrow between two squares */
1202 DrawArrowBetweenSquares (int s_col, int s_row, int d_col, int d_row)
1204 int s_x, s_y, d_x, d_y;
1206 if( s_col == d_col && s_row == d_row ) {
1210 /* Get source and destination points */
1211 SquareToPos( s_row, s_col, &s_x, &s_y);
1212 SquareToPos( d_row, d_col, &d_x, &d_y);
1215 d_y += squareSize / 2 - squareSize / 4; // [HGM] round towards same centers on all sides!
1217 else if( d_y < s_y ) {
1218 d_y += squareSize / 2 + squareSize / 4;
1221 d_y += squareSize / 2;
1225 d_x += squareSize / 2 - squareSize / 4;
1227 else if( d_x < s_x ) {
1228 d_x += squareSize / 2 + squareSize / 4;
1231 d_x += squareSize / 2;
1234 s_x += squareSize / 2;
1235 s_y += squareSize / 2;
1238 A_WIDTH = squareSize / 14.; //[HGM] make float
1240 DrawArrowBetweenPoints( s_x, s_y, d_x, d_y );
1241 ArrowDamage(s_col, s_row, d_col, d_row);
1245 IsDrawArrowEnabled ()
1247 return (appData.highlightMoveWithArrow || twoBoards && partnerUp) && squareSize >= 32;
1251 DrawArrowHighlight (int fromX, int fromY, int toX,int toY)
1253 if( IsDrawArrowEnabled() && fromX >= 0 && fromY >= 0 && toX >= 0 && toY >= 0)
1254 DrawArrowBetweenSquares(fromX, fromY, toX, toY);