3 Copyright (c) Scott Gasch
11 Static exchange evaluator and support code. See also x86.asm.
19 $Id: see.c 348 2008-01-05 07:11:36Z scott $
25 #define ADD_ATTACKER(p, c, v) \
26 pList->data[pList->uCount].pPiece = (p); \
27 pList->data[pList->uCount].cLoc = (c); \
28 pList->data[pList->uCount].uVal = (v); \
32 SlowGetAttacks(IN OUT SEE_LIST *pList,
40 SlowGetAttacks is the C version of GetAttacks; it should be
41 identical to the GetAttacks code in x86.asm. The job of the
42 function is, given a position, square and side, to populate the
43 SEE_LIST with the locations and types of enemy pieces attacking
48 SEE_LIST *pList : list to populate
49 POSITION *pos : the board
50 COOR cSquare : square in question
51 ULONG uSide : side we are looking for attacks from
65 static PIECE pPawn[2] = { BLACK_PAWN, WHITE_PAWN };
66 static int iSeeDelta[2] = { -17, +15 };
69 ASSERT(IS_ON_BOARD(cSquare));
70 ASSERT(IS_VALID_COLOR(uSide));
71 VerifyPositionConsistency(pos, FALSE);
76 // Check for pawns attacking cSquare
78 c = cSquare + (iSeeDelta[uSide]);
81 p = pos->rgSquare[c].pPiece;
82 if (p == pPawn[uSide])
85 // N.B. Don't use ADD_ATTACKER here because we know we're
88 pList->data[0].pPiece = p;
89 pList->data[0].cLoc = c;
90 pList->data[0].uVal = VALUE_PAWN;
98 p = pos->rgSquare[c].pPiece;
99 if (p == pPawn[uSide])
101 ADD_ATTACKER(p, c, VALUE_PAWN);
106 // Check for pieces attacking cSquare
108 for (x = pos->uNonPawnCount[uSide][0] - 1;
112 c = pos->cNonPawns[uSide][x];
113 ASSERT(IS_ON_BOARD(c));
115 p = pos->rgSquare[c].pPiece;
116 ASSERT(p && !IS_PAWN(p));
117 ASSERT(GET_COLOR(p) == uSide);
119 iIndex = (int)c - (int)cSquare;
120 if (0 == (CHECK_VECTOR_WITH_INDEX(iIndex, GET_COLOR(p)) &
121 (1 << PIECE_TYPE(p))))
126 if (IS_KNIGHT_OR_KING(p))
128 ASSERT(IS_KNIGHT(p) || IS_KING(p));
129 ADD_ATTACKER(p, c, PIECE_VALUE(p));
134 // Check to see if there is a piece in the path from cSquare
135 // to c that blocks the attack.
137 iDelta = NEG_DELTA_WITH_INDEX(iIndex);
138 ASSERT(iDelta == -1 * CHECK_DELTA_WITH_INDEX(iIndex));
140 for (cBlockIndex = cSquare + iDelta;
142 cBlockIndex += iDelta)
144 if (!IS_EMPTY(pos->rgSquare[cBlockIndex].pPiece))
151 // Nothing in the way.
153 ADD_ATTACKER(p, c, PIECE_VALUE(p));
162 // SEE_HEAPS works great in principle but makes MinLegalPiece
163 // inaccurate if the top of the heap is not a legal move (i.e. the
164 // piece is pinned etc...) When tested the result of this was minimal
165 // and the use of a heap instead of a sorted list sped up the SEE
168 #define PARENT(x) ((x - 1) / 2)
169 #define LEFT_CHILD(x) (((x) * 2) + 1)
170 #define RIGHT_CHILD(x) (((x) * 2) + 2)
174 _IsValidHeap(IN SEE_LIST *p)
179 Is the minheap in SEE_LIST p->data[] valid (i.e. does it satisfy
180 the minheap property?)
184 SEE_LIST *p : SEE_LIST to check
188 static FLAG : TRUE if valid, FALSE otherwise
194 for (u = 0; u < p->uCount; u++)
197 if ((l < p->uCount) && (p->data[l].uVal < p->data[u].uVal))
203 if ((r < p->uCount) && (p->data[r].uVal < p->data[u].uVal))
214 _PushDown(IN OUT SEE_LIST *p,
220 Take heap node number u and compare its value with the value of
221 its children. Swap smallest value into position u and continue
222 the push-down process if warranted.
226 SEE_LIST *p : SEE_LIST/minheap
227 ULONG u : node number in consideration
235 ULONG l = LEFT_CHILD(u);
241 // The heap is a complete tree -- if there's no left child of this
242 // node then there's no right child either. If this is a leaf node
243 // then our work is done.
245 ASSERT(p->uCount > 0);
250 ASSERT(PARENT(l) == u);
253 // Otherwise, find the smallest of u, l and r.
256 ASSERT(r == RIGHT_CHILD(u));
257 ASSERT(PARENT(r) == u);
258 ASSERT((r != u) && (r != l) && (l != u));
260 if (p->data[l].uVal < p->data[u].uVal)
264 if ((r < p->uCount) && (p->data[r].uVal < p->data[uSmallest].uVal))
270 // If it's anything other than u, swap them and continue to push.
274 ASSERT((uSmallest == l) || (uSmallest == r));
275 temp = p->data[uSmallest];
276 p->data[uSmallest] = p->data[u];
278 _PushDown(p, uSmallest);
283 _BuildHeap(IN OUT SEE_LIST *p)
288 Convert a random array into a minheap. Start at the first
289 internal node and push it down into place. Continue to work
290 backwards until we get to the root (index 0).
294 SEE_LIST *p : list to heapify
302 int iStart = (p->uCount / 2) - 1;
305 ASSERT(iStart < (int)p->uCount);
306 for (i = iStart; i > -1; i--)
309 _PushDown(p, (ULONG)i);
311 ASSERT(_IsValidHeap(p));
316 _BubbleUp(IN OUT SEE_LIST *p, IN ULONG u)
321 Take a node and bubble it up into place to re-create a minheap.
326 ULONG u : index of node to bubble up
337 ASSERT(p->uCount > 0);
341 ASSERT((LEFT_CHILD(uParent) == u) ||
342 (RIGHT_CHILD(uParent) == u));
344 if (p->data[uParent].uVal > p->data[u].uVal)
346 temp = p->data[uParent];
347 p->data[uParent] = p->data[u];
349 _BubbleUp(p, uParent);
356 _SortList(IN OUT SEE_LIST *pList)
361 Sort the SEE list by piece value with a selection sort.
375 register ULONG uMaxVal;
376 register ULONG uMaxLoc;
384 // Assume index X is the largest value item in the list
386 uMaxVal = pList->data[x].uVal;
390 // Look for others with a larger value
396 if (pList->data[y].uVal > uMaxVal)
398 uMaxVal = pList->data[y].uVal;
403 sTemp = pList->data[uMaxLoc];
404 pList->data[uMaxLoc] = pList->data[x];
405 pList->data[x] = sTemp;
412 _RemoveItem(IN OUT SEE_LIST *pList,
418 Delete item x from the list.
435 ASSERT(x < pList->uCount);
436 ASSERT(pList->uCount > 0);
440 // Swap item x with the last thing on the heap and then push the
443 if (x != (pList->uCount - 1))
445 pList->data[x] = pList->data[pList->uCount - 1];
457 // If X is not the last thing in the list we will have to ripple
458 // shift to close the hole. This is rare.
464 pList->data[y - 1] = pList->data[y];
473 _ClearPieceByLocation(IN OUT SEE_LIST *pList,
479 Find a piece on the SEE list at COOR cLoc and delete it.
494 while (x < pList->uCount)
496 if (pList->data[x].cLoc == cLoc)
498 _RemoveItem(pList, x);
505 // This can happen if, for example, the SEE move was a passed pawn push
512 _MinLegalPiece(IN POSITION *pos,
522 Return the piece from the SEE list with the lowest value that is
523 not pinned to its own king. Because we are storing the SEE_LISTS
524 as heaps, this is only an approximate value in the event that the
525 real min value piece is indeed pinned to its own king. I
526 considered sorting the list in this case but it seems like (in
527 tests) this inaccuracy really has little or no impact on the
528 search tree size. The speedup with heaps instead of sorted lists
529 seems worth the price.
533 POSITION *pos : the board
534 ULONG uColor : the color on move
535 SEE_LIST *pList : the list we're selecting from
536 SEE_LIST *pOther : the other side's list
552 // The list is a minheap with the min value piece at index 0.
558 p = pList->data[x].pPiece;
561 // If this piece is the king, then no need to see if the move
562 // exposes the king to check.. just play the move as long as
563 // it's legal (i.e. no defenders to the square)
567 if (pOther->uCount == 0)
569 *pc = pList->data[0].cLoc;
572 // Note: if p is a king and we allow them to play it then
573 // by definition the other side has nothing to counter
574 // with... otherwise we'd be moving into check here. So
575 // even if we had other pieces that were pinned to the
576 // king, empty out the list because we're done in the next
586 // Otherwise... consider pins. If the least valuable
587 // defender of the square cannot move because doing so
588 // would exposes his king to check, skip it and try the
589 // next least valuable.
591 cKing = pos->cNonPawns[uColor][0];
592 c = ExposesCheck(pos, pList->data[x].cLoc, cKing);
595 // cIgnore is the coordinate of the last piece the other
596 // side "moved" in this capture sequence. This is a hack
597 // to ignore pins based on a piece that has already moved
598 // in the computation but is already on the board. This
599 // of course does not work for positions where the piece
600 // you expose check to was "moved" two turns ago but these
603 if (!IS_ON_BOARD(c) || (c == cIgnore))
605 *pc = pList->data[x].cLoc;
606 _RemoveItem(pList, x);
613 // There are no legal pieces to move to square
621 _MinLegalPiece(IN POSITION *pos,
631 Return the piece from the SEE list with the lowest value that is
632 not pinned to its own king.
636 POSITION *pos : the board
637 ULONG uColor : the color on move
638 SEE_LIST *pList : the list we're selecting from
639 SEE_LIST *pOther : the other side's list
655 // The list is sorted from most valuable (index 0) to least valuable
656 // (index N). Begin at the least valuable and work up.
658 for (x = pList->uCount - 1;
662 p = pList->data[x].pPiece;
664 // If this piece is the king, then no need to see if the move
665 // exposes the king to check.. just play the move as long as
666 // it's legal (i.e. no defenders to the square)
668 if ((IS_KING(p)) && (pOther->uCount == 0))
671 *pc = pList->data[0].cLoc;
678 // Otherwise... consider pins. If the least valuable
679 // defender of the square cannot move because doing so
680 // would exposes his king to check, skip it and try the
681 // next least valuable.
683 cKing = pos->cNonPawns[uColor][0];
684 c = ExposesCheck(pos, pList->data[x].cLoc, cKing);
687 // cIgnore is the coordinate of the last piece the other
688 // side "moved" in this capture sequence. This is a hack
689 // to ignore pins based on a piece that has already moved
690 // in the computation but is already on the board. This
691 // of course does not work for positions where the piece
692 // you expose check to was "moved" two turns ago but these
695 if (!IS_ON_BOARD(c) || (c == cIgnore))
697 *pc = pList->data[x].cLoc;
698 _RemoveItem(pList, x);
705 // There are no legal pieces to move to square
711 static void _AddXRays(IN POSITION *pos,
712 IN INT iAttackerColor,
715 IN OUT SEE_LIST *pAttacks,
716 IN OUT SEE_LIST *pDefends)
721 We just "moved" a piece in the SEE sequence... add any xray
722 attacks that it exposed to the SEE list to be take part as the
745 iIndex = (int)cTarget - (int)cObstacle;
748 // If there is no way for a queen sitting on the target square to
749 // reach the obsticle square then there is no discovered attack.
750 // (This could happen, for instance, if a knight captured. It
751 // can't uncover a new attack on the square where it took.
753 if (0 == (CHECK_VECTOR_WITH_INDEX(iIndex, BLACK) & (1 << QUEEN)))
759 // The squares are on the same rank, file or diagonal. iDelta is
760 // the way to move from target towards obstacle.
762 iDelta = CHECK_DELTA_WITH_INDEX(iIndex);
765 // Search for a piece that moves the right way to attack the target
766 // square starting at the obstacle square + delta.
768 for (cIndex = cObstacle + iDelta;
772 xPiece = pos->rgSquare[cIndex].pPiece;
773 if (!IS_EMPTY(xPiece))
776 // Does it move the right way to hit cTarget? TODO: can this
777 // be optimized? Remember pawns though...
779 if (0 != (CHECK_VECTOR_WITH_INDEX((int)cIndex - (int)cTarget,
781 (1 << PIECE_TYPE(xPiece))))
784 // Add this attacker to the proper SEE_LIST
786 if (GET_COLOR(xPiece) == iAttackerColor)
788 pAttacks->data[pAttacks->uCount].pPiece = xPiece;
789 pAttacks->data[pAttacks->uCount].cLoc = cIndex;
790 pAttacks->data[pAttacks->uCount].uVal =
793 ASSERT(pAttacks->uCount > 0);
795 _BubbleUp(pAttacks, pAttacks->uCount - 1);
802 pDefends->data[pDefends->uCount].pPiece = xPiece;
803 pDefends->data[pDefends->uCount].cLoc = cIndex;
804 pDefends->data[pDefends->uCount].uVal =
807 ASSERT(pDefends->uCount > 0);
809 _BubbleUp(pDefends, pDefends->uCount - 1);
822 SEE(IN POSITION *pos,
828 Given a board and a move on the board, estimate the value of the
829 move by considering the friend/enemy pieces that attack the move's
839 SCORE : the estimate of the move's score
843 SEE_LIST rgPieces[2];
848 ULONG uWhoseTurn = GET_COLOR(mv.pMoved);
849 ULONG uOrig = uWhoseTurn;
853 COOR cFrom = mv.cFrom;
854 static FLAG _Table[2][3] = {
856 { FALSE, FALSE, TRUE }, // uVal==0
857 { TRUE, FALSE, FALSE }, // uVal==1
861 ASSERT(mv.uMove != 0);
862 memset(rgiList, 0xFF, sizeof(rgiList));
863 memset(rgPieces, 0xFF, sizeof(rgPieces));
867 // Create a sorted list of pieces attacking and defending the
870 GetAttacks(&(rgPieces[uWhoseTurn]), pos, mv.cTo, uWhoseTurn);
871 GetAttacks(&(rgPieces[FLIP(uWhoseTurn)]), pos, mv.cTo, FLIP(uWhoseTurn));
873 _BuildHeap(&(rgPieces[FLIP(uWhoseTurn)]));
874 _BuildHeap(&(rgPieces[uWhoseTurn]));
876 _SortList(&(rgPieces[FLIP(uWhoseTurn)]));
877 _SortList(&(rgPieces[uWhoseTurn]));
881 // Play the first move -- TODO: the first move may be illegal...
884 rgiList[0] = (PIECE_VALUE(mv.pCaptured) +
885 PIECE_VALUE(mv.pPromoted));
886 uInPeril = (PIECE_VALUE(mv.pMoved) +
887 PIECE_VALUE(mv.pPromoted));
890 _ClearPieceByLocation(&(rgPieces[uWhoseTurn]), mv.cFrom);
895 &(rgPieces[uWhoseTurn]),
896 &(rgPieces[FLIP(uWhoseTurn)]));
904 // Other side's turn now...
906 uWhoseTurn = FLIP(uWhoseTurn);
908 pPiece = _MinLegalPiece(pos,
910 &(rgPieces[uWhoseTurn]),
911 &(rgPieces[FLIP(uWhoseTurn)]),
914 if (0 == pPiece) break; // no legal piece
916 // If this is a pawn capturing and it ends on the queening
917 // rank, set uPromValue appropriately. Bitwise operators are
918 // correct here, done for speed and branch removal; this loop
919 // is pretty heavily used.
921 uPromValue = IS_PAWN(pPiece) & (RANK1(mv.cTo) | RANK8(mv.cTo));
922 uPromValue *= VALUE_QUEEN;
923 ASSERT((uPromValue == 0) || (uPromValue == VALUE_QUEEN));
925 ASSERT(uListIndex != 0);
926 rgiList[uListIndex] = rgiList[uListIndex - 1] +
927 iSign * (uInPeril + uPromValue);
929 ASSERT(uListIndex < ARRAY_LENGTH(rgiList));
930 uInPeril = PIECE_VALUE(pPiece) + uPromValue;
933 GET_COLOR(mv.pMoved),
936 &(rgPieces[uOrig]), // These must be rgAttacks and
937 &(rgPieces[FLIP(uOrig)])); // rgDefends. Not based on tomove
942 // The swaplist is now complete but we still must consider that either
943 // side has the option of not taking (not continuing the exchange).
945 ASSERT(uListIndex >= 1);
948 while (uListIndex > 0)
950 uVal = (uListIndex & 1);
951 iSign = ((rgiList[uListIndex] > rgiList[uListIndex - 1]) -
952 (rgiList[uListIndex] < rgiList[uListIndex - 1])) + 1;
953 ASSERT((iSign >= 0) && (iSign <= 2));
954 if (TRUE == _Table[uVal][iSign])
956 rgiList[uListIndex - 1] = rgiList[uListIndex];
962 iSign = DebugSEE(pos, mv);
964 if ((rgiList[0] != iSign) &&
965 (iSign != INVALID_SCORE))
969 Trace("Real SEE says: %d\n"
970 "Test SEE says: %d\n",
972 UtilPanic(TESTCASE_FAILURE,